¿Cuál es tu mejor práctica para ejecutar scripts de una sola vez?
El Problema
Todos nos hemos encontrado en una situación así, y muchas preguntas en este sitio necesitan una solución como esta. Ya sea que tengas que actualizar una base de datos, insertar muchos datos automáticamente, convertir meta_keys
, o algo similar.
Por supuesto, en un sistema en funcionamiento basado en mejores prácticas esto no debería suceder.
Pero como sucede, me encantaría escuchar tu solución personal a este problema y por qué elegiste la tuya.
La Pregunta
¿Cómo implementas scripts de una sola vez en tu instalación de WordPress (en funcionamiento)?
El problema aquí se debe principalmente a las siguientes razones:
- Los scripts que insertan datos no deberían ejecutarse más de una vez
- Los scripts que requieren muchos recursos no deberían ejecutarse en un momento en que no puedan ser monitoreados
- No deberían ejecutarse por accidente
La Razón por la que Pregunto
Tengo mi propia práctica, la voy a publicar en las respuestas. Como no sé si es la mejor solución que existe, me gustaría conocer la tuya. Además, esta es una pregunta que se hace muchas veces en el contexto de otras preguntas, y sería genial tener un recurso que recopile las ideas.
¡espero aprender de ustedes! :)

Yo personalmente uso una combinación de:
- un archivo dedicado al script de una sola ejecución
- usar un transient para evitar que el script se ejecute accidentalmente más de una vez
- usar gestión de capacidades o control de usuario para asegurar que solo yo ejecute el script.
Estructura
Uso un archivo (onetime.php
) en mi carpeta de includes inc
, el cual se incluye en el functions.php
, y se elimina de ahí después de su uso.
include( 'inc/onetime.php' );
El archivo para el script en sí
En mi onetime.php
coloco mi función f711_my_onetime_function()
. Podría ser cualquier función. Asumo que tu script está probado y funciona correctamente.
Para tener control sobre la ejecución del script, uso ambos
Control de capacidades
Para evitar que otros usuarios ejecuten accidentalmente mi script:
if ( current_user_can( 'manage_options' ) ) // verificar permisos de administrador
o
if ( get_current_user_id() == 711 ) // verificar si soy yo - prefiero restringir la ejecución solo a mí, no a todos los admins.
Un transient
para evitar que yo mismo ejecute accidentalmente el script más de una vez.
$transient = 'f711_my_onetime_check';
if ( !get_transient( $transient ) ) // verificar si la función no se ha ejecutado.
El archivo para ejecutar el script en mi función f711_my_onetime_function()
luciría así:
$transient = 'f711_my_onetime_check';
if ( get_current_user_id() == 711 && !get_transient( $transient ) ) {
set_transient( $transient, 'locked', 600 ); // bloquear función por 10 minutos
add_action( 'wp_footer', 'f711_my_onetime_function' ); // ejecutar mi función en el hook deseado.
}
function f711_my_onetime_function() {
// toda mi gloriosa magia de una sola vez.
}
La razón por la que establezco el transient inmediatamente después de verificar si existe es porque quiero que la función se ejecute después de que el script haya sido bloqueado para evitar usos dobles.
Si necesito algún resultado de mi función, lo imprimo como un comentario en el footer, o a veces incluso filtro el contenido.
El tiempo de bloqueo está configurado a 10 minutos, pero puede ajustarse según tus necesidades.
Limpieza
Después de la ejecución exitosa de mi script, elimino el include
del functions.php
, y quito el onetime.php
del servidor. Como usé un tiempo de espera para el transient, no necesito limpiar la base de datos, pero por supuesto también podrías eliminar el transient después de quitar el archivo.

También puedes hacer esto:
ejecuta onetime.php
y renómbralo después de la ejecución.
if ( current_user_can( 'manage_options' ) ) {
if( ! file_exists( '/ruta/a/onetime.php' ) )
return;
add_action( 'wp_footer', 'ravs_my_onetime_function' ); // ejecuta mi función en el hook deseado.
}
function ravs_my_onetime_function() {
// toda mi gloriosa magia de una sola vez.
include( '/ruta/a/onetime.php' );
// después de toda la ejecución renombra tu archivo;
rename( '/ruta/a/onetime.php', '/ruta/a/onetime-backup.php');
}

Creé un script Phing de línea de comandos para esto, no es nada especial aparte de cargar un script externo para ejecutarse. La razón por la que lo usé a través de la CLI es porque:
- No quiero que se cargue por error (necesita escribir un comando)
- Es seguro ya que se puede ejecutar fuera del directorio web, en otras palabras, puede afectar a WP pero WP no puede acceder al script de ninguna manera.
- No añade ningún código a WP o a la base de datos en sí.
require('..ruta a ../wp-blog-header.php');
//un montón de variables globales de WP
define('WP_USE_THEMES', false);
//código personalizado
Así que puedes usar Phing, o la CLI de PHP y dormir tranquilo. WP-CLI también es una buena alternativa, aunque olvido si puedes usarlo fuera del directorio web.
Ya que este es un post popular, aquí hay un ejemplo del script: https://github.com/wycks/WordPhing (run.php)

En condiciones ideales, me conectaría por SSH al servidor y ejecutaría la función yo mismo usando wp-cli.
Aunque esto no siempre es posible, así que suelo establecer una variable $_GET y engancharla a 'init', por ejemplo:
add_action( 'init', function() {
if( isset( $_GET['one_time'] ) && $_GET['one_time'] == 'an_unlikely_string' ) {
do_the_one_time_thing();
}
});
luego acceder a
http://my_blog.com/?one_time=an_unlikely_string
y desactivar el hook una vez que haya terminado.

Otra forma bastante simple de ejecutar un script de una sola vez es hacerlo mediante un plugin MU.
Coloca el código en algún archivo PHP (por ejemplo, one-time.php
) que subas a la carpeta de plugins MU (por defecto /wp-content/mu-plugins
), ajusta los permisos del archivo, ejecuta el plugin (es decir, según el hook que hayas elegido, básicamente solo tienes que visitar el frontend/backend), y ya estarás listo.
Aquí tienes una plantilla:
/**
* Clase principal (y única).
*/
class OneTimeScript {
/**
* Hook para la función del plugin.
*
* @type string
*/
public static $hook = 'init';
/**
* Prioridad de la función del plugin.
*
* @type int
*/
public static $priority = 0;
/**
* Ejecuta el script de una sola vez.
*
* @hook self::$hook
* @return void
*/
public static function run() {
// acción de una sola vez va aquí...
// limpieza
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // function run
/**
* Elimina el archivo.
*
* @hook shutdown
* @return void
*/
public static function unlink() {
unlink(__FILE__);
} // function unlink
} // class OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);
Sin los comentarios y demás, simplemente se ve así:
class OneTimeScript {
public static $hook = 'init';
public static $priority = 0;
public static function run() {
// acción de una sola vez va aquí...
add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
} // function run
public static function unlink() {
unlink(__FILE__);
} // function unlink
} // class OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);

Definitivamente puedes hacerlo, solo crea tu código de un solo uso como un plugin.
add_action('admin_init', 'one_time_call');
function one_time_call()
{
/* TUS SCRIPTS */
deactivate_plugins('onetime/index.php'); //desactivar el plugin actual
}
El problema es ¿cómo activo este plugin sin hacer clic en el enlace Activar?
Simplemente agrega activate_plugins('onetime/index.php');
en functions.php
o Usa plugins obligatorios, http://codex.wordpress.org/Must_Use_Plugins
Prueba con diferentes acciones según cuándo quieras ejecutar el plugin de un solo uso:
admin_init - después de la inicialización del admin
init - inicialización de WordPress
wp - cuando WordPress esté cargado

A veces he utilizado una función enganchada a la desactivación de un plugin.
Mira aquí Actualizar enlaces antiguos a permalinks bonitos para Custom Post Types
Como solo los administradores pueden activar plugins, hay una comprobación de capacidades como efecto secundario.
No hay necesidad de borrar el archivo una vez desactivado, WordPress no lo incluirá. Además, si quieres ejecutarlo de nuevo puedes hacerlo. Activando y desactivando nuevamente.
Y a veces he utilizado transitorios como en la respuesta de @fischi. Por ejemplo aquí consulta para crear productos de WooCommerce desde imágenes o aquí Eliminar/reemplazar etiquetas img en el contenido de posts para publicaciones automáticas
Una combinación de ambos puede ser una alternativa.

Esta también es una muy buena idea. Si resulta molesto tener que activarlo y desactivarlo constantemente, también podrías enganchar la misma función a la activación del plugin, ¿verdad?

Sí, si lo deseas. Sin embargo, creo que hacer 2 clics no es un gran esfuerzo para ejecutar un script de una sola vez. Cualquier otra solución que implique comandos CLI o manipulación de archivos (renombrar, eliminar) requiere más "trabajo". Además, cada vez que dependes de hooks, estás confiando en variables globales, añadiendo una capa adicional de posibles problemas relacionados con la seguridad/predictibilidad del código. @fischi

Otra forma es establecer una opción global de wp_option cuando el trabajo esté hecho y verificar esa opción cada vez que se ejecute el hook init.
function my_one_time_function() {
// Salir si el trabajo ya se ha realizado.
if ( get_option( 'my_one_time_function', '0' ) == '1' ) {
return;
}
/***** REALIZA TU TRABAJO UNA SOLA VEZ *****/
// Añadir o actualizar la opción wp_option
update_option( 'my_one_time_function', '1' );
}
add_action( 'init', 'my_one_time_function' );
Naturalmente, no necesitas mantener este código para siempre (aunque sea solo una simple lectura de la base de datos), por lo que probablemente puedas eliminarlo cuando el trabajo esté hecho. También puedes cambiar manualmente el valor de esta opción a 0 si necesitas volver a ejecutar el código.

Usar wp-cli eval-file
es excelente. Incluso puedes hacerlo en un sistema remoto (usando un alias ssh con '@') con un script local.
Si tienes tu código en one-time.php
en el directorio base de WordPress y tienes acceso por línea de comandos al sistema donde quieres ejecutarlo, puedes hacer:
wp eval-file one-time.php
Si tienes el archivo one-time.php
localmente y quieres ejecutarlo contra un WordPress remoto usando @, se vería así:
wp @remote eval-file - < one-time.php

Mi enfoque es un poco diferente en esto. Me gusta añadir mis scripts de una sola vez como una función en el function.php de mi tema y ejecutarlos con una consulta GET específica.
if ( isset($_GET['linkupdate']) ) {
add_action('init', 'link_update', 10);
}
function link_update() {
// Script de una sola ejecución
die;
}
Para ejecutar esto, simplemente visita la URL "www.sitename.com/?linkupdate"
Hasta ahora, este método me ha funcionado bien...
¿Tiene este método algún inconveniente? Solo por curiosidad...

Solo uso una única página de plantilla de producto personalizada que no estoy usando y que no está conectada a nada en el servidor público.
Por ejemplo, si tengo una página de testimonios que no está en vivo (en modo borrador o similar), pero está conectada a una plantilla de página única, como single-testimonial.php
, puedo colocar funciones ahí, cargar la página mediante un preview
y la función o lo que sea se ejecuta una vez. También es muy fácil hacer modificaciones a la función en caso de depuración.
Es realmente sencillo y lo prefiero sobre usar init
porque tengo más control sobre cuándo y cómo se ejecuta. Simplemente es mi preferencia.

Por si ayuda, esto es lo que hice y funciona bien:
add_action( 'init', 'upsubscriptions_setup');
function upsubscriptions_setup()
{
$version = get_option('upsubscriptions_setup_version');
// Si no hay versión registrada aún en la base de datos
if (!$version) {
add_option('upsubscriptions_setup_version', '0.1');
$version = get_option('upsubscriptions_setup_version');
}
if (version_compare($version, "0.1") <= 0) {
// hacer cosas
update_option('upsubscriptions_setup_version', '0.2');
}
if (version_compare($version, "0.2") <= 0) {
// hacer cosas
update_option('upsubscriptions_setup_version', '0.3');
}
if (version_compare($version, "0.3") <= 0) {
// hacer cosas
update_option('upsubscriptions_setup_version', '0.4');
}
// etc
}
