Organizando código no arquivo functions.php do seu tema WordPress?

92

Quanto mais personalização eu faço no WordPress, mais eu começo a pensar se devo organizar este arquivo ou dividi-lo.

Mais especificamente, se eu tiver várias funções personalizadas que se aplicam apenas à área de administração e outras que se aplicam ao meu site público, existe algum motivo para incluir todas as funções de administração em seu próprio arquivo ou agrupá-las?

Dividi-los em arquivos separados ou agrupá-los possivelmente acelera um site WordPress ou o WordPress / PHP ignora automaticamente funções que têm um prefixo de código is_admin?

Qual é a melhor maneira de lidar com um grande arquivo de funções (o meu tem 1370 linhas).

NetConstructor.com
fonte

Respostas:

120

Se você está chegando ao ponto em que o código no seu tema functions.phpestá começando a sobrecarregá-lo, eu definitivamente diria que você está pronto para considerar dividi-lo em vários arquivos. Eu costumo fazer isso quase por uma segunda natureza neste momento.

Use incluir arquivos no do seu tema functions.phpArquivo

Crio um subdiretório chamado "includes" no meu diretório de temas e segmento meu código em arquivos de inclusão organizados pelo que faz sentido para mim no momento (o que significa que estou constantemente refatorando e movendo o código conforme o site evolui). coloque qualquer código real functions.php; tudo vai nos arquivos de inclusão; apenas minha preferência.

Apenas para dar um exemplo, aqui está a minha instalação de teste que eu uso para testar minhas respostas às perguntas aqui no WordPress Answers. Sempre que respondo a uma pergunta, mantenho o código por perto, caso precise dele novamente. Isso não é exatamente o que você fará em um site ativo, mas mostra a mecânica de dividir o código:

<?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');  

Ou criar plugins

Outra opção é começar a agrupar seu código por função e criar seus próprios plugins. Para mim, começo a codificar no functions.phparquivo do tema e, quando eu aprofundei o código, mudei a maior parte do meu código para plugins.

No entanto, nenhum ganho de desempenho significativo da organização de código PHP

Por outro lado, estruturar seus arquivos PHP é 99% sobre a criação de ordem e manutenção e 1% sobre desempenho, se isso (organização .jse .cssarquivos chamados pelo navegador via HTTP é um caso completamente diferente e tem implicações enormes no desempenho). Mas como você organiza seu código PHP no servidor praticamente não importa da perspectiva de desempenho.

E a organização do código é a preferência pessoal

E por último, mas não menos importante, a organização do código é a preferência pessoal. Algumas pessoas odeiam como eu organizo o código, assim como eu também odeio. Encontre algo que você goste e permaneça com ele, mas permita que sua estratégia evolua ao longo do tempo à medida que você aprende mais e se sente mais confortável com ela.

MikeSchinkel
fonte
Boa resposta, acabei de chegar a esse ponto em que preciso dividir o arquivo de funções. Quando você acha útil mudar do frunctions.php para um plugin. Você disse na sua resposta: no momento em que eu recebo o código detalhado, mudei a maior parte do meu código para plugins . Eu não entendo isso completamente, o que você quer dizer com aprofundado.
precisa saber é o seguinte
5
+1 para "ou criar plug-ins". Mais especificamente, " plugins de funcionalidade "
Ian Dunn
3
usando caminhos relativos pode não ser confiável em todos os tipos de configurações, caminho absoluto deve sempre ser usado em vez
Mark Kaplun
2
@ MarkKaplun - Você está absolutamente correto. Desde que escrevi essa resposta, aprendi essa lição da maneira mais difícil. Vou atualizar minha resposta. Obrigado por apontar isso.
MikeSchinkel 23/09
Eu recebo "Uso de DIR constante indefinido - assumido ' DIR ' em C: \ wamp \ www \ site \ conteúdo-wp \ temas \ meu-tema \ funções.php" - PHP v5.6.25 e PHP v7.0.10 - não posso formato corretamente este DIR no comentário (underscoreunderscoreDIRunderscoreunderscore), mas funciona com dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko
50

Resposta atrasada

Como incluir seus arquivos da maneira certa:

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' );

O mesmo funciona em plugins também.

Como obter o caminho certo ou URi

Veja também as funções da API do sistema de arquivos, como:

  • 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.

Como reduzir o número de include/require

Se você precisar buscar todos os arquivos de um diretório, vá com

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

Lembre-se de que isso ignora falhas (talvez boas para uso em produção) / arquivos não carregáveis.

Para alterar esse comportamento, convém usar uma configuração diferente durante o desenvolvimento:

$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;

Editar: Abordagem OOP / SPL

Como acabei de voltar e vi que essa resposta está ficando cada vez mais positiva, pensei em mostrar como estou fazendo isso hoje em dia - em um mundo PHP 5.3+. O exemplo a seguir carrega todos os arquivos de uma subpasta de temas denominada src/. É aqui que tenho minhas bibliotecas que lidam com certas tarefas, como menus, imagens etc. Você nem precisa se preocupar com o nome, pois cada arquivo é carregado. Se você tiver outras subpastas nesse diretório, elas serão ignoradas.

O \FilesystemIteratoré o supercedor do PHP 5.3+ sobre o \DirectoryIterator. Ambos fazem parte do PHP SPL. Enquanto o PHP 5.2 tornou possível desativar a extensão SPL integrada (abaixo de 1% de todas as instalações), o SPL agora faz parte do núcleo do PHP.

<?php

namespace Theme;

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

Anteriormente, enquanto eu ainda suportava o PHP 5.2.x, usei a seguinte solução: A \FilterIteratorno src/Filtersdiretório para recuperar apenas arquivos (e não apontar pontos das pastas) e a \DirectoryIteratorpara executar o loop e o carregamento.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

O \FilterIteratorfoi tão fácil como isso:

<?php

namespace Theme\Filters;

class IncludesFilter extends \FilterIterator
{
    public function accept()
    {
        return
            ! $this->current()->isDot()
            and $this->current()->isFile()
            and $this->current()->isReadable();
    }
}

Além do PHP 5.2 estar morto / EOL até agora (e 5.3), há o fato de que há mais código e mais um arquivo no jogo, então não há razão para usar o posterior e dar suporte ao PHP 5.2.x.

Resumido

Um artigo ainda mais detalhado pode ser encontrado aqui no WPKrauts .

EDIT A maneira obviamente correta é usar o namespacecódigo d, preparado para o carregamento automático do PSR-4 , colocando tudo no diretório apropriado que já está definido através do namespace. Em seguida, basta usar o Composer e a composer.jsonpara gerenciar suas dependências e deixá-lo construir automaticamente o seu carregador automático de PHP (que importa automaticamente um arquivo apenas chamando use \<namespace>\ClassName). Esse é o padrão de fato no mundo PHP, o caminho mais fácil e ainda mais pré-automatizado e simplificado pelo WP Starter .

kaiser
fonte
5

em termos de desmembramento, na minha placa da caldeira eu uso uma função personalizada para procurar uma pasta chamada funções no diretório do tema, se não estiver lá, ela será criada. Então, cria uma matriz de todos os arquivos .php encontrados nessa pasta (se houver) e executa um include (); em cada um deles.

Dessa forma, toda vez que preciso escrever uma nova funcionalidade, basta adicionar um arquivo PHP à pasta de funções e não precisar me preocupar em codificá-lo no 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);
    }
  }

}
Fuzz leve
fonte
5
@mildfuzz : Bom truque. Pessoalmente, eu não o usaria para código de produção, porque ele faz para cada carregamento de página o que poderíamos facilmente fazer uma vez ao lançar o site. Além disso, eu adicionaria de alguma forma para omitir arquivos, como não carregar nada que comece com um sublinhado, para que eu ainda possa armazenar trabalhos em andamento no diretório do tema. Caso contrário, legal!
MikeSchinkel 6/09/10
adoro a ideia, mas concordo que isso pode levar a um carregamento desnecessário para cada solicitação. Alguma idéia se haveria uma maneira simples de gerar o arquivo final functions.php automaticamente armazenado em cache com algum tipo de atualização se / quando novos arquivos forem adicionados ou em um intervalo de tempo específico?
NetConstructor.com
Bom, mas isso leva a inflexibilidades. O que acontece se um invasor conseguir soltar seu código lá? E se a ordem das inclusões for importante?
Tom J Nowell
1
@ MikeSchinkel Acabei de chamar meus arquivos de trabalho de foo._php e, em seguida, solto o _php quando quero que ele seja executado.
poeira de papel leve
@ NetConstructor: Também estaria interessado em alguma solução.
Kaiser #
5

Eu gosto de usar uma função para os arquivos dentro de uma pasta. Essa abordagem facilita a adição de novos recursos ao adicionar novos arquivos. Mas eu escrevo sempre em classe ou com namespaces - dou mais controle sobre o Namespace de funções, método etc.

Abaixo um pequeno exemplo; ut também usa o acordo sobre a classe * .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;

}

Em Temas, uso frequentemente outro cenário. Defino a função do arquivo externel em um ID de suporte, veja o exemplo. Isso é útil se for fácil desativar o feture do arquivo externo. Eu uso a função principal do WP require_if_theme_supports()e ele carrega apenas, se o ID do suporte estiver ativo. No exemplo a seguir, defini esse ID suportado na linha antes de carregar o arquivo.

    /**
     * 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'
    );

Você pode ver mais disso no repositório deste tema .

bueltge
fonte
4

Eu gerencio um site com cerca de 50 tipos de página personalizados exclusivos em diferentes idiomas do servidor em uma instalação de rede. Juntamente com uma tonelada de plugins.

Nós fomos forçados a dividir tudo em algum momento. Um arquivo de funções com 20 a 30k linhas de código não é nada engraçado.

Decidimos concluir a refatoração de todo o código para gerenciar melhor a base de código. A estrutura padrão do tema wordpress é boa para sites pequenos, mas não para sites maiores.

Nosso novo functions.php contém apenas o necessário para iniciar o site, mas nada que pertença a uma página específica.

O layout do tema que usamos agora é semelhante ao padrão de design do MCV, mas em um estilo de codificação processual.

Por exemplo, nossa página de membro:

page-member.php . Responsável pela inicialização da página. Chamando as funções corretas do ajax ou similar. Pode ser equivalente à parte do controlador no estilo MCV.

funções-membro.php . Contém todas as funções relacionadas a esta página. Isso também está incluído em outras páginas do servidor que precisam de funções para nossos membros.

content-member.php . Prepara os dados para HTML Pode ser equivalente ao modelo no MCV.

layout-member.php . A parte HTML.

Depois que fizemos essas alterações, o tempo de desenvolvimento caiu facilmente em 50% e agora o proprietário do produto tem problemas para fornecer novas tarefas. :)

Patrik Grinsvall
fonte
7
Para tornar isso mais útil, considere mostrar como esse padrão MVC realmente funciona.
Kaiser #
eu também gostaria de ver um exemplo de sua abordagem, de preferência com alguns detalhes / várias situações. A abordagem parece muito interessante. Você comparou a carga / desempenho do servidor com a metodologia padrão que outros usam? forneça um exemplo no github, se possível.
NetConstructor.com 10/10
3

Do arquivo filho themes.php:

    require_once( get_stylesheet_directory() . '/inc/custom.php' );
Brad Dalton
fonte
0

No functions.php, uma maneira mais elegante de chamar um arquivo necessário seria:

require_once local_template ('/ inc / functions / shortcodes.php');

Ideias imperativas
fonte
4
locate_template()tem um terceiro parâmetro ...
fuxia
0

Eu combinei @kaiser 's e @mikeschinkel respostas' s.

Eu tenho todas as minhas personalizações para o meu tema em uma /includespasta e, dentro dessa pasta, tenho tudo dividido em subpastas.

Eu só quero /includes/admine seu sub-conteúdo seja incluído quandotrue === is_admin()

Se uma pasta for excluída iterator_check_traversal_callbackao retornar false, seus subdiretórios não serão iterados (ou passados ​​para 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();
}
seangwright
fonte