Cómo estructurar un plugin
Esta no es una pregunta sobre cómo construir un plugin de WordPress. Más bien, trata sobre qué guías, si las hay, se podrían aplicar a cómo organizar la arquitectura de archivos de cualquier plugin.
Algunos otros lenguajes de programación o bibliotecas tienen formas muy controladas de organizar directorios y archivos. A veces esto es molesto y resalta la libertad que ofrece PHP, pero por otro lado, los plugins de WordPress se ensamblan de cualquier manera según lo determine su autor.
No hay una respuesta correcta, pero mi esperanza es refinar cómo yo y otros construimos plugins para hacerlos más amigables para que otros desarrolladores los analicen, sean más fáciles de depurar, más fáciles de navegar y posiblemente más eficientes.
La pregunta final: ¿cuál crees que es la mejor manera de organizar un plugin?
A continuación se muestran algunas estructuras de ejemplo, pero de ninguna manera es una lista exhaustiva. Siéntete libre de agregar tus propias recomendaciones.
Estructura Predeterminada Asumida
/wp-content
/plugins
/my-plugin
my-plugin.php
Método Modelo Vista Controlador (MVC)
/wp-content
/plugins
/my-plugin
/controller
Controller.php
/model
Model.php
/view
view.php
my-plugin.php
Las tres partes del MVC:
- El modelo interactúa con la base de datos, consultando y guardando datos, y contiene la lógica.
- El controlador contendría etiquetas de plantilla y funciones que utilizaría la vista.
- La vista es responsable de mostrar los datos proporcionados por el modelo según lo construido por el controlador.
Método organizado por tipo
/wp-content
/plugins
/my-plugin
/admin
admin.php
/assets
css/
images/
/classes
my-class.php
/lang
my-es_ES.mo
/templates
my-template.php
/widgets
my-widget.php
my-plugin.php
WordPress Plugin Boilerplate
Disponible en Github
Basado en la API de Plugins, Estándares de Codificación, y Estándares de Documentación.
/wp-content
/plugins
/my-plugin
/admin
/css
/js
/partials
my-plugin-admin.php
/includes
my_plugin_activator.php
my_plugin_deactivator.php
my_plugin_i18n.php
my_plugin_loader.php
my_plugin.php
/languages
my_plugin.pot
/public
/css
/js
/partials
my-plugin-public.php
LICENSE.txt
README.txt
index.php
my-plugin.php
uninstall.php
Método organizado libremente
/wp-content
/plugins
/my-plugin
css/
images/
js/
my-admin.php
my-class.php
my-template.php
my-widget.php
my-plugin.php

Tenga en cuenta que todos los plugins son "controladores" según los estándares de WP.
Depende de lo que el plugin deba hacer, pero en todos los casos intentaría separar la salida en pantalla del código PHP tanto como sea posible.
Aquí hay una forma fácil de hacerlo: primero, define una función que cargue la plantilla:
function my_plugin_load_template(array $_vars){
// no puedes permitir que locate_template cargue tu plantilla
// porque los desarrolladores de WP se aseguraron de que no puedas pasar
// variables a tu plantilla :(
$_template = locate_template('my_plugin', false, false);
// usa la predeterminada si el tema no la tiene
if(!_$template)
$_template = 'views/template.php';
// cárgala
extract($_vars);
require $template;
}
Ahora, si el plugin usa un widget para mostrar datos:
class Your_Widget extends WP_Widget{
...
public function widget($args, $instance){
$title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
// este widget muestra los últimos 5 "películas"
$posts = new WP_Query(array('posts_per_page' => 5, 'post_type' => 'movie'));
if($title)
print $before_title . $title . $after_title;
// aquí dependemos de la plantilla para mostrar los datos en pantalla
my_plugin_load_template(array(
// variables que deseas exponer en la plantilla
'posts' => $posts,
));
print $before_widget;
}
...
}
La plantilla:
<?php while($posts->have_posts()): $posts->the_post(); ?>
<p><?php the_title(); ?></p>
<?php endwhile; ?>
Archivos:
/plugins/my_plugin/plugin.php <-- solo hooks
/plugins/my_plugin/widget.php <-- clase del widget, si tienes un widget
/themes/twentyten/my_plugin.php <-- plantilla
/plugins/my_plugin/views/template.php <-- plantilla de respaldo
Dónde coloques tu CSS, JS, imágenes o cómo diseñes el contenedor para los hooks es menos importante. Supongo que es cuestión de preferencia personal.

Depende del plugin. Esta es mi estructura básica para casi todos los plugins:
my-plugin/
inc/
Cualquier archivo PHP adicional específico del plugin va aquí
lib/
Clases de biblioteca, css, js y otros archivos que uso con muchos
plugins van aquí
css/
js/
images/
lang/
Archivos de traducción
my-plugin.php
readme.txt
Esto sería algo que iría en la carpeta lib
.
Si es un plugin particularmente complejo, con mucha funcionalidad en el área de administración, agregaría una carpeta admin
para contener todos esos archivos PHP. Si el plugin hace algo como reemplazar archivos del tema incluidos, podría haber una carpeta template
o theme
también.
Entonces, una estructura de directorios podría verse así:
my-plugin/
inc/
lib/
admin/
templates/
css/
js/
images/
lang/
my-plugin.php
readme.txt

¿También incluirías los archivos CSS y JS del administrador dentro de la carpeta /admin? ¿Por lo tanto, tendrías otra carpeta /css y /js dentro de /admin?

En mi humilde opinión, la ruta más fácil, potente y mantenible es usar una estructura MVC, y WP MVC está diseñado para hacer que escribir plugins MVC sea muy sencillo (aunque soy un poco parcial...). Con WP MVC, simplemente creas los modelos, vistas y controladores, y todo lo demás se maneja automáticamente detrás de escena.
Se pueden crear controladores y vistas separados para las secciones públicas y de administración, y todo el framework aprovecha muchas de las características nativas de WordPress. La estructura de archivos y gran parte de la funcionalidad son exactamente iguales a las de los frameworks MVC más populares (Rails, CakePHP, etc.).
Puedes encontrar más información y un tutorial aquí:

Estamos utilizando una combinación de todos los métodos. En primer lugar, usamos el Zend Framework 1.11 en nuestros plugins y por lo tanto tuvimos que usar una estructura similar para los archivos de clases debido al mecanismo de autocarga.
La estructura de nuestro plugin principal (que sirve como base para todos nuestros plugins) es similar a esta:
webeo-core/
css/
images/
js/
languages/
lib/
Webeo/
Core.php
Zend/
/** archivos de ZF **/
Loader.php
views/
readme.txt
uninstall.php
webeo-core.php
- WordPress llama al archivo
webeo-core.php
en la carpeta raíz del plugin. - En este archivo configuramos la ruta de inclusión de PHP y registramos los hooks de activación y desactivación del plugin.
- También tenemos una clase
Webeo_CoreLoader
dentro de este archivo, que establece algunas constantes del plugin, inicializa el autocargador de clases y realiza una llamada al método setup de la claseCore.php
dentro de la carpetalib/Webeo
. Esto se ejecuta en el hook de acciónplugins_loaded
con una prioridad de9
. - La clase
Core.php
es nuestro archivo de arranque del plugin. El nombre se basa en el nombre del plugin.
Como puedes ver, tenemos un subdirectorio dentro de la carpeta lib
para todos nuestros paquetes de proveedores (Webeo
, Zend
). Todos los subpaquetes dentro de un proveedor están estructurados por el módulo en sí. Para un nuevo formulario de administración de Configuración de Correo
, tendríamos la siguiente estructura:
webeo-core/
...
lib/
Webeo/
Form/
Admin/
MailSettings.php
Admin.php
Core.php
Form.php
Nuestros sub-plugins tienen la misma estructura con una excepción. Vamos un nivel más profundo dentro de la carpeta del proveedor para resolver conflictos de nombres durante el evento de autocarga. También llamamos a la clase de arranque del plugin Ejemplo: Faq.php
con prioridad 10
dentro del hook plugins_loaded
.
webeo-faq/ (usa/extiende webeo-core)
css/
images/
js/
languages/
lib/
Webeo/
Faq/
Faq.php
/** todos los archivos de clase relevantes del plugin **/
views/
readme.txt
uninstall.php
webeo-faq.php
Probablemente en la próxima versión cambiaré el nombre de la carpeta lib
a vendors
y moveré todas las carpetas públicas (css, images, js, languages) a una carpeta llamada public
.

Como muchos ya han respondido aquí, realmente depende de lo que se supone que debe hacer el plugin, pero aquí está mi estructura base:
my-plugin/
admin/
contiene todos los archivos administrativos del backend
js/
contiene todos los archivos JavaScript del backend
css/
contiene todos los archivos CSS del backend
images/
contiene todas las imágenes del backend
admin_file_1.php archivo de funcionalidad del backend
admin_file_2.php otro archivo de funcionalidad del backend
js/
contiene todos los archivos JavaScript para el frontend
css/
contiene todos los archivos CSS para el frontend
inc/
contiene todas las clases auxiliares
lang/
contiene todos los archivos de traducción
images/
contiene todas las imágenes para el frontend
my-plugin.php archivo principal del plugin con metadatos, principalmente includes, hooks de acciones y filtros
readme.txt
changelog.txt
license.txt

Me inclino por la siguiente estructura de plugin, aunque suele variar dependiendo de los requisitos específicos del plugin.
wp-content/
plugins/
mi-plugin/
inc/
Archivos específicos solo para este plugin
admin/
Archivos para tareas administrativas
lib/
Aquí van las clases de biblioteca/helpers
css/
Archivos CSS para el plugin
js/
Archivos JavaScript
images/
Imágenes para mi plugin
lang/
Archivos de traducción
plugin.php
Este es el archivo principal que incluye/llama a otros archivos
README
Normalmente coloco los detalles de la licencia aquí además de información útil
Todavía no he creado un plugin para WordPress que requiera una arquitectura estilo MVC, pero si tuviera que hacerlo, lo estructuraría con un directorio MVC separado que a su vez contenga views/controllers/models.

Todos mis plugins siguen esta estructura, que parece ser muy similar a lo que hacen la mayoría de otros desarrolladores:
carpeta-del-plugin/
admin/
css/
imagenes/
js/
core/
css/
imagenes/
js/
languages/
library/
templates/
carpeta-del-plugin.php
readme.txt
changelog.txt
license.txt
El archivo carpeta-del-plugin.php es usualmente una clase que carga todos los archivos requeridos desde la carpeta core/. La mayoría de las veces en el hook init o plugins_loaded.
Solía prefijar todos mis archivos también, pero como @kaiser mencionó anteriormente, es realmente redundante y recientemente decidí eliminarlo de cualquier plugin futuro.
La carpeta library/ contiene todas las librerías externas auxiliares de las que el plugin podría depender.
Dependiendo del plugin, podría haber un archivo uninstall.php en la raíz del plugin también. La mayoría de las veces esto se maneja mediante register_uninstall_hook(), sin embargo.
Obviamente, algunos plugins podrían no requerir archivos de administración o plantillas, etc, pero la estructura anterior funciona para mí. Al final solo tienes que encontrar una estructura que funcione para ti y luego mantenerla.
También tengo un plugin de inicio, basado en la estructura anterior que uso como punto de partida para todos mis plugins. Todo lo que necesito hacer entonces es una búsqueda/reemplazo para los prefijos de funciones/clases y listo. Cuando todavía estaba prefijando mis archivos ese era un paso extra que tenía que hacer (y bastante molesto por cierto), pero ahora solo tengo que renombrar la carpeta del plugin y el archivo principal del plugin.

Mi lógica es que cuanto más grande es el plugin, más estructura utilizo.
Para plugins grandes tiendo a usar MVC.
Uso esto como punto de partida y omito lo que no sea necesario.
controller/
frontend.php
wp-admin.php
widget1.php
widget2.php
model/
standard-wp-tables.php // si es necesario, dividirlo
custom-tabel1.php
custom-tabel2.php
view/
helper.php
frontend/
archivos...php
wp-admin/
archivos...php
widget1/
archivo...php
widget2/
archivo...php
css/
js/
image/
library/ //solo php, principalmente para Zend Framework, nuevamente si es necesario
constants.php //tiendo a usarlo con frecuencia
plugin.php //archivo de inicialización
install-unistall.php //solo en plugins grandes

También, revisa este excelente boilerplate para widgets de WP. Proporciona excelentes pistas sobre las estructuras (incluso si no hay una clase ni carpeta para modelos separados).

Un enfoque menos común para estructurar los archivos y directorios de un plugin es el enfoque por tipo de archivo. Vale la pena mencionarlo aquí para completar la información:
plugin-name/
js/
sparkle.js
shake.js
css/
style.css
scss/
header.scss
footer.scss
php/
class.php
functions.php
plugin-name.php
uninstall.php
readme.txt
Cada directorio contiene solo archivos de ese tipo. Es importante señalar que este enfoque se queda corto cuando tienes muchos tipos de archivos .png .gif .jpg
que podrían organizarse de manera más lógica bajo un solo directorio, como por ejemplo images/
.

¡He desarrollado una plantilla de repositorio en GitHub para plugins de WordPress, encapsulando más de 10 años de experiencia en un esquema estructurado!
https://github.com/EdwardBock/wordpress-plugin-starterkit
La plantilla cumple con los estándares PSR-4 para minimizar los includes de strings y utiliza namespaces para un nombrado de clases conciso y una clase de componente de plantillas. También incluye un archivo docker compose para poder desarrollar el plugin de forma aislada. Pero también puede colocarse en un proyecto existente y funcionar sin problemas.
¡Disfruta explorando y no dudes en contribuir!
