Organizing Code in your WordPress Theme’s functions.php File?

The more customization I make to WordPress the more I start thinking about if I should be organizing this file or splitting it up.

More specifically, if I have a bunch of custom functions which only apply to the admin area and others which just apply to my public website is there any reason to possibly include all admin functions within their own file or group them together?

Read More

Would splitting them up into separate files or grouping them together possibly speed up a WordPress website or does WordPress/PHP automatically skip over functions which have an is_admin code prefix?

What’s the best way to deal with a large functions file (mine is 1370 lines long).

Related posts

Leave a Reply

8 comments

  1. If you are getting to the point where the code in your theme’s functions.php is starting to overwhelm you I would definitely say you are ready to consider splitting it up into multiple files. I tend to do that almost by second nature at this point.

    Use Include Files in your Theme’s functions.php File

    I create a subdirectory called “includes” under my theme directory and segment my code into include files organized by what makes sense to me at the time (which means I’m constantly refactoring and moving code around as a site evolves.) I also rarely put any real code in functions.php; everything goes in the include files; just my preference.

    Just to give you an example here’s my test install that I use to test my answers to questions here on WordPress Answers. Every time I answer a question I keep the code around in case I need it again. This isn’t exactly what you’ll do for a live site but it shows the mechanics of splitting up the code:

    <?php 
    /*
     * functions.php
     * 
     */
    require_once( __DIR__ . '/includes/null-meta-compare.php');
    require_once( __DIR__ . '/includes/older-examples.php');
    require_once( __DIR__ . '/includes/wp-admin-menu-classes.php');
    require_once( __DIR__ . '/includes/admin-menu-function-examples.php');
    
    // WA: Adding a Taxonomy Filter to Admin List for a Custom Post Type?
    // http://wordpress.stackexchange.com/questions/578/
    require_once( __DIR__ . '/includes/cpt-filtering-in-admin.php'); 
    require_once( __DIR__ . '/includes/category-fields.php');
    require_once( __DIR__ . '/includes/post-list-shortcode.php');
    require_once( __DIR__ . '/includes/car-type-urls.php');
    require_once( __DIR__ . '/includes/buffer-all.php');
    require_once( __DIR__ . '/includes/get-page-selector.php');
    
    // http://wordpress.stackexchange.com/questions/907/
    require_once( __DIR__ . '/includes/top-5-posts-per-category.php'); 
    
    // http://wordpress.stackexchange.com/questions/951/
    require_once( __DIR__ . '/includes/alternate-category-metabox.php');  
    
    // http://lists.automattic.com/pipermail/wp-hackers/2010-August/034384.html
    require_once( __DIR__ . '/includes/remove-status.php');  
    
    // http://wordpress.stackexchange.com/questions/1027/removing-the-your-backup-folder-might-be-visible-to-the-public-message-generate
    require_once( __DIR__ . '/includes/301-redirects.php');  
    

    Or Create Plugins

    Another option it to start grouping your code by function and create your own plugins. For me I start coding in the theme’s functions.php file and by the time I get the code fleshed out I’ve moved most of my code into plugins.

    However NO Significant Performance Gain From PHP Code Organization

    On the other hand structuring your PHP files is 99% about creating order and maintainability and 1% about performance, if that (organizing .js and .css files called by the browser via HTTP is a completely different case and has huge performance implications.) But how you organize your PHP code on the server pretty much doesn’t matter from a performance perspective.

    And Code Organization is Personal Preference

    And last but not least code organization is personal preference. Some people would hate how I organize code just as I might hate how they do it too. Find something you like and stick with it, but allow your strategy to evolve over time as you learn more and get more comfortable with it.

  2. Late answer

    How to include your files the right way:

    function wpse1403_bootstrap()
    {
        // Here we load from our includes directory
        // This considers parent and child themes as well    
        locate_template( array( 'inc/foo.class.php' ), true, true );
    }
    add_action( 'after_setup_theme', 'wpse1403_bootstrap' );
    

    The same works in plugins too.

    How to get the right path or URi

    Also take a look at file system API functions like:

    • home_url()
    • plugin_dir_url()
    • plugin_dir_path()
    • admin_url()
    • get_template_directory()
    • get_template_directory_uri()
    • get_stylesheet_directory()
    • get_stylesheet_directory_uri()
    • etc.

    How to reduce the number of include/require

    If you need to fetch all files from a directory go with

    foreach ( glob( 'path/to/folder/*.php' ) as $file )
        include $file;
    

    Keep in mind that this ignores failures (maybe good for production use)/not loadable files.

    To alter this behavior you might want to use a different config during development:

    $files = ( defined( 'WP_DEBUG' ) AND WP_DEBUG )
        ? glob( 'path/to/folder/*.php', GLOB_ERR )
        : glob( 'path/to/folder/*.php' )
    
    foreach ( $files as $file )
        include $file;
    

    Edit: OOP/SPL approach

    As I just came back and saw that this answer is getting more and more upvotes, I thought I might show how I’m doing it nowadays – in a PHP 5.3+ world. The following example loads all files from a themes subfolder named src/. This is where I have my libraries that handle certain tasks like menus, images, etc. You don’t even have to care about the name as every single file gets loaded. If you have other subfolders in this directory, they get ignored.

    The FilesystemIterator is the PHP 5.3+ supercedor over the DirectoryIterator. Both are part of the PHP SPL. While PHP 5.2 made it possible to turn the built in SPL extension off (below 1% of all installs did that), the SPL now is part of PHP core.

    <?php
    
    namespace Theme;
    
    $files = new FilesystemIterator( __DIR__.'/src', FilesystemIterator::SKIP_DOTS );
    foreach ( $files as $file )
    {
        /** @noinspection PhpIncludeInspection */
        ! $files->isDir() and include $files->getRealPath();
    }
    

    Previously while I still supported PHP 5.2.x, I used the following solution: A FilterIterator in the src/Filters directory to only retrieve files (and not dot pointers of folders) and a DirectoryIterator to do the looping and loading.

    namespace Theme;
    
    use ThemeFiltersIncludesFilter;
    
    $files = new IncludesFilter( new DirectoryIterator( __DIR__.'/src' ) );
    foreach ( $files as $file )
    {
        include_once $files->current()->getRealPath();
    }
    

    The FilterIterator was as easy as that:

    <?php
    
    namespace ThemeFilters;
    
    class IncludesFilter extends FilterIterator
    {
        public function accept()
        {
            return
                ! $this->current()->isDot()
                and $this->current()->isFile()
                and $this->current()->isReadable();
        }
    }
    

    Aside from PHP 5.2 being dead/EOL by now (and 5.3 as well), there’s the fact that it’s more code and one more file in the game, so there’s no reason to go with the later and support PHP 5.2.x.

    Summed up

    EDIT The obviously correct way is to use namespaced code, prepared for PSR-4 autoloading by putting everything in the appropriate directory that already is defined via the namespace. Then just use Composer and a composer.json to manage your dependencies and let it auto-build your PHP autoloader (that imports automatically a file by just calling use <namespace>ClassName). That’s the de-facto standard in the PHP world, the easiest way to go and even more pre-automated and simplified by WP Starter.

  3. I like to use a function to the files inside a folder. This approach makes it easy to add new features when adding new files. But I write always in class or with namespaces – give it more control about the Namespace of functions, method etc.

    Below a small example; ut also useage with the agreement about the class*.php

    public function __construct() {
    
        $this->load_classes();
    }
    
    /**
     * Returns array of features, also
     * Scans the plugins subfolder "/classes"
     *
     * @since   0.1
     * @return  void
     */
    protected function load_classes() {
    
        // load all files with the pattern class-*.php from the directory classes
        foreach( glob( dirname( __FILE__ ) . '/classes/class-*.php' ) as $class )
            require_once $class;
    
    }
    

    In Themes I use often a other scenario. I define the function of the externel file in a support ID, see the example. That is usefull if I will easy deactivate the feture of the externel file. I use the WP core function require_if_theme_supports() and he load only, if the support ID was active. In the follow example I deifned this supported ID in the line before load the file.

        /**
         * Add support for Theme Customizer
         * 
         * @since  09/06/2012
         */
        add_theme_support( 'documentation_customizer', array( 'all' ) );
        // Include the theme customizer for options of theme options, if theme supported
        require_if_theme_supports( 
            'documentation_customizer',
            get_template_directory() . '/inc/theme-customize.php'
        );
    

    You can see more of this in the repo of this theme.

  4. in terms of breaking it up, in my boiler plate I use a custom function to look for a folder called functions in the theme directory, if it is not there it creates it. Then is creates an array of all the .php files it finds in that folder (if any) and runs an include(); on each of them.

    That way, each time I need to write some new functionality, I just add a PHP file to the functions folder, and don’t have to worry about coding it into the site.

    <?php
    /* 
    FUNCTIONS for automatically including php documents from the functions folder.
    */
    //if running on php4, make a scandir functions
    if (!function_exists('scandir')) {
      function scandir($directory, $sorting_order = 0) {
        $dh = opendir($directory);
        while (false !== ($filename = readdir($dh))) {
          $files[] = $filename;
        }
        if ($sorting_order == 0) {
          sort($files);
        } else {
          rsort($files);
        }
        return ($files);
      }
    }
    /*
    * this function returns the path to the funtions folder.
    * If the folder does not exist, it creates it.
    */
    function get_function_directory_extension($template_url = FALSE) {
      //get template url if not passed
      if (!$template_url)$template_url = get_bloginfo('template_directory');
    
    
      //replace slashes with dashes for explode
      $template_url_no_slash = str_replace('/', '.', $template_url);
    
      //create array from URL
      $template_url_array = explode('.', $template_url_no_slash);
    
      //--splice array
    
      //Calculate offset(we only need the last three levels)
      //We need to do this to get the proper directory, not the one passed by the server, as scandir doesn't work when aliases get involved.
      $offset = count($template_url_array) - 3;
    
      //splice array, only keeping back to the root WP install folder (where wp-config.php lives, where the front end runs from)
      $template_url_array = array_splice($template_url_array, $offset, 3);
      //put back togther as string
      $template_url_return_string = implode('/', $template_url_array);
      fb::log($template_url_return_string, 'Template'); //firephp
    
      //creates current working directory with template extention and functions directory    
      //if admin, change out of admin folder before storing working dir, then change back again.
      if (is_admin()) {
        $admin_directory = getcwd();
        chdir("..");
        $current_working_directory = getcwd();
        chdir($admin_directory);
      } else {
        $current_working_directory = getcwd();
      }
      fb::log($current_working_directory, 'Directory'); //firephp
    
      //alternate method is chdir method doesn't work on your server (some windows servers might not like it)
      //if (is_admin()) $current_working_directory = str_replace('/wp-admin','',$current_working_directory);
    
      $function_folder = $current_working_directory . '/' . $template_url_return_string . '/functions';
    
    
      if (!is_dir($function_folder)) mkdir($function_folder); //make folder, if it doesn't already exist (lazy, but useful....ish)
      //return path
      return $function_folder;
    
    }
    
    //removed array elements that do not have extension .php
    function only_php_files($scan_dir_list = false) {
      if (!$scan_dir_list || !is_array($scan_dir_list)) return false; //if element not given, or not array, return out of function.
      foreach ($scan_dir_list as $key => $value) {
        if (!strpos($value, '.php')) {
    
          unset($scan_dir_list[$key]);
        }
      }
      return $scan_dir_list;
    }
    //runs the functions to create function folder, select it,
    //scan it, filter only PHP docs then include them in functions
    
    add_action('wp_head', fetch_php_docs_from_functions_folder(), 1);
    function fetch_php_docs_from_functions_folder() {
    
      //get function directory
      $functions_dir = get_function_directory_extension();
      //scan directory, and strip non-php docs
      $all_php_docs = only_php_files(scandir($functions_dir));
    
      //include php docs
      if (is_array($all_php_docs)) {
        foreach ($all_php_docs as $include) {
          include($functions_dir . '/' . $include);
        }
      }
    
    }
    
  5. I manage a site with about 50 unique custom page types in serveral different languages over a network installation. Along with a TON of plugins.

    We where forced to split it all up at some point. A functions file with 20-30k lines of code is not funny at all.

    We decided to completley refactor all code in order to manage the codebase better. The default wordpress theme structure is good for small sites, but not for bigger sites.

    Our new functions.php only contains what is necessary to start the site, but nothing which belongs to a specific page.

    The theme layout we use now is similar to the MCV design pattern, but in a procedural coding style.

    For example our member page:

    page-member.php. Responsible for initializing the page. Calling the correct ajax functions or similar. Could be equivialent to the Controller part in the MCV style.

    functions-member.php. Contains all functions related to this page. This is also included in serveral other pages which need functions for our members.

    content-member.php. Prepares the data for HTML Could be equivialent to the Model in MCV.

    layout-member.php. The HTML part.

    Efter we did these changes the development time have easily dropped by 50% and now the product owner have trouble giving us new tasks. 🙂

  6. I combined @kaiser‘s and @mikeschinkel‘s answers.

    I have all my customizations to my theme in a /includes folder and within that folder I have everything broken out into sub folders.

    I only want /includes/admin and its sub-contents to be included when true === is_admin()

    If a folder is excluded in iterator_check_traversal_callback by returning false then its sub-directories will not be iterated (or passed to iterator_check_traversal_callback)

    /**
     *  Require all customizations under /includes
     */
    $includes_import_root = 
        new RecursiveDirectoryIterator( __DIR__ . '/includes', FilesystemIterator::SKIP_DOTS );
    
    function iterator_check_traversal_callback( $current, $key, $iterator ) {
        $file_name = $current->getFilename();
    
        // Only include *.php files
        if ( ! $current->isDir() ) {
            return preg_match( '/^.+.php$/i', $file_name );
        }
    
        // Don't include the /includes/admin folder when on the public site
        return 'admin' === $file_name
            ? is_admin()
            : true;
    }
    
    $iterator_filter = new RecursiveCallbackFilterIterator(
        $includes_import_root, 'iterator_check_traversal_callback'
    );
    
    foreach ( new RecursiveIteratorIterator( $iterator_filter ) as $file ) {
        include $file->getRealPath();
    }