Fase-00: Configuración inicial del proyecto
- Configuración de Composer con PSR-4 en _testing-suite/ - Configuración de PHPUnit en _testing-suite/phpunit.xml - Configuración de PHPCS en _testing-suite/phpcs.xml - WordPress Test Suite integrado en bootstrap-integration.php - Scripts de backup automatizado (backup-database.php, backup-files.bat) - Procedimientos de rollback documentados (RESTORE-PROCEDURE.md) - Estrategia de Git branching documentada (GIT-BRANCHING-STRATEGY.md) - .gitignore actualizado para excluir _testing-suite/ y _planeacion/ - Limpieza de estructura anterior de Clean Architecture - Documentación completa en _planeacion/roi-theme/_MIGRACION-CLEAN-ARCHITECTURE/Fase-00/ Archivos de configuración movidos a _testing-suite/: - composer.json (PSR-4 autoloading, dev dependencies) - phpunit.xml (3 suites: Unit, Integration, E2E) - phpcs.xml (WordPress Coding Standards) - bootstrap-unit.php y bootstrap-integration.php Sistema de backup implementado con 4 mejoras críticas: - Password protegido con --defaults-file - Verificación de espacio en disco - Lock files para prevenir ejecuciones concurrentes - Detección automática de rutas Procedimientos de rollback documentados: - Rollback de base de datos (5-10 min) - Rollback de archivos (10-15 min) - Rollback de Git (5 min) - Rollback completo (20-30 min) Preparación del entorno completa. Listo para comenzar Fase-1.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -70,6 +70,9 @@ composer.lock
|
||||
# Planning and documentation
|
||||
_planeacion/
|
||||
|
||||
# Testing infrastructure (composer, phpunit, phpcs configs and dependencies)
|
||||
_testing-suite/
|
||||
|
||||
# Claude Code tools
|
||||
.playwright-mcp/
|
||||
.serena/
|
||||
|
||||
@@ -6,49 +6,93 @@
|
||||
*
|
||||
* @package ROI_Theme
|
||||
* @since 2.2.0
|
||||
* @deprecated 2.0.0 Use ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository instead
|
||||
* @see ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository
|
||||
*
|
||||
* Esta clase será eliminada en la versión 3.0.0
|
||||
* Por favor migre su código a la nueva arquitectura Clean Architecture
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ROI_DB_Manager
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
class ROI_DB_Manager {
|
||||
|
||||
/**
|
||||
* Nombre de la tabla de componentes (sin prefijo)
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
const TABLE_COMPONENTS = 'roi_theme_components';
|
||||
|
||||
/**
|
||||
* Nombre de la tabla de defaults (sin prefijo)
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
const TABLE_DEFAULTS = 'roi_theme_components_defaults';
|
||||
|
||||
/**
|
||||
* Versión de la base de datos
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
const DB_VERSION = '1.0';
|
||||
|
||||
/**
|
||||
* Opción para almacenar la versión de la DB
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
const DB_VERSION_OPTION = 'roi_db_version';
|
||||
|
||||
/**
|
||||
* @var \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter
|
||||
*/
|
||||
private $adapter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
_deprecated_function(
|
||||
__CLASS__ . '::__construct',
|
||||
'2.0.0',
|
||||
'ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository'
|
||||
);
|
||||
|
||||
$this->logDeprecation(__CLASS__, __FUNCTION__);
|
||||
|
||||
// Hook para verificar/actualizar DB en cada carga
|
||||
add_action('admin_init', array($this, 'maybe_create_tables'));
|
||||
|
||||
// Inicializar adapter para mantener compatibilidad
|
||||
$this->adapter = $this->getLegacyAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener nombre completo de tabla con prefijo
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*
|
||||
* @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto)
|
||||
* @return string Nombre completo de la tabla con prefijo
|
||||
*/
|
||||
public function get_table_name($table_type = 'components') {
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'Direct database access not recommended - use repositories'
|
||||
);
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ($table_type === 'defaults') {
|
||||
@@ -60,8 +104,11 @@ class ROI_DB_Manager {
|
||||
|
||||
/**
|
||||
* Verificar si las tablas necesitan ser creadas o actualizadas
|
||||
*
|
||||
* @deprecated 2.0.0 Tables are now managed through database migrations
|
||||
*/
|
||||
public function maybe_create_tables() {
|
||||
// Keep for backward compatibility but tables are managed differently now
|
||||
$installed_version = get_option(self::DB_VERSION_OPTION);
|
||||
|
||||
if ($installed_version !== self::DB_VERSION) {
|
||||
@@ -72,17 +119,22 @@ class ROI_DB_Manager {
|
||||
|
||||
/**
|
||||
* Crear tablas personalizadas
|
||||
* Crea tanto la tabla de componentes (personalizaciones) como la de defaults
|
||||
*
|
||||
* @deprecated 2.0.0 Use DatabaseMigrator service instead
|
||||
*/
|
||||
public function create_tables() {
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'ROITheme\Infrastructure\Services\DatabaseMigrator'
|
||||
);
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
|
||||
$success = true;
|
||||
|
||||
// Estructura común para ambas tablas
|
||||
// Table structure (kept for backward compatibility)
|
||||
$table_structure = "(
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
@@ -98,192 +150,169 @@ class ROI_DB_Manager {
|
||||
INDEX idx_updated (updated_at)
|
||||
) $charset_collate;";
|
||||
|
||||
// Crear tabla de componentes (personalizaciones del usuario)
|
||||
// Create components table
|
||||
$table_components = $this->get_table_name('components');
|
||||
$sql_components = "CREATE TABLE $table_components $table_structure";
|
||||
$sql_components = "CREATE TABLE IF NOT EXISTS $table_components $table_structure";
|
||||
dbDelta($sql_components);
|
||||
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_components'") === $table_components) {
|
||||
error_log("ROI DB Manager: Tabla $table_components creada/actualizada exitosamente");
|
||||
} else {
|
||||
error_log("ROI DB Manager: Error al crear tabla $table_components");
|
||||
$success = false;
|
||||
}
|
||||
|
||||
// Crear tabla de defaults (valores por defecto del tema)
|
||||
// Create defaults table
|
||||
$table_defaults = $this->get_table_name('defaults');
|
||||
$sql_defaults = "CREATE TABLE $table_defaults $table_structure";
|
||||
$sql_defaults = "CREATE TABLE IF NOT EXISTS $table_defaults $table_structure";
|
||||
dbDelta($sql_defaults);
|
||||
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_defaults'") === $table_defaults) {
|
||||
error_log("ROI DB Manager: Tabla $table_defaults creada/actualizada exitosamente");
|
||||
} else {
|
||||
error_log("ROI DB Manager: Error al crear tabla $table_defaults");
|
||||
$success = false;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar si una tabla existe
|
||||
*
|
||||
* @param string $table_type Tipo de tabla: 'components' o 'defaults'
|
||||
* @return bool True si la tabla existe
|
||||
* @deprecated 2.0.0
|
||||
*
|
||||
* @param string $table_type Tipo de tabla
|
||||
* @return bool True si existe, false si no
|
||||
*/
|
||||
public function table_exists($table_type = 'components') {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name($table_type);
|
||||
return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
|
||||
$query = $wpdb->prepare('SHOW TABLES LIKE %s', $table_name);
|
||||
return $wpdb->get_var($query) === $table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar configuración de un componente
|
||||
* Save component configuration
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave de configuración
|
||||
* @param mixed $config_value Valor de configuración
|
||||
* @param string $data_type Tipo de dato (string, boolean, integer, json)
|
||||
* @param string $version Versión del tema
|
||||
* @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto)
|
||||
* @return bool|int ID del registro o false en caso de error
|
||||
* @deprecated 2.0.0 Use SaveComponentUseCase::execute() instead
|
||||
*
|
||||
* @param string $component_name Component name (e.g., 'top_notification_bar', 'navbar', 'footer', 'hero_section')
|
||||
* @param string $config_key Configuration key
|
||||
* @param mixed $config_value Configuration value
|
||||
* @param string $data_type Data type (string, boolean, integer, array)
|
||||
* @param string|null $version Schema version
|
||||
* @param string $table_type Table type (components or defaults)
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function save_config($component_name, $config_key, $config_value, $data_type = 'string', $version = null, $table_type = 'components') {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name($table_type);
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'ROITheme\Application\UseCases\SaveComponent\SaveComponentUseCase::execute()'
|
||||
);
|
||||
|
||||
// Convertir valor según tipo
|
||||
if ($data_type === 'json' && is_array($config_value)) {
|
||||
$config_value = json_encode($config_value, JSON_UNESCAPED_UNICODE);
|
||||
} elseif ($data_type === 'boolean') {
|
||||
$config_value = $config_value ? '1' : '0';
|
||||
}
|
||||
$this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args());
|
||||
|
||||
// Usar ON DUPLICATE KEY UPDATE para INSERT o UPDATE
|
||||
$result = $wpdb->query($wpdb->prepare(
|
||||
"INSERT INTO $table_name (component_name, config_key, config_value, data_type, version, updated_at)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_value = VALUES(config_value),
|
||||
data_type = VALUES(data_type),
|
||||
version = VALUES(version),
|
||||
updated_at = VALUES(updated_at)",
|
||||
// Delegar al adapter para mantener funcionalidad
|
||||
return $this->adapter->save_config(
|
||||
$component_name,
|
||||
$config_key,
|
||||
$config_value,
|
||||
$data_type,
|
||||
$version,
|
||||
current_time('mysql')
|
||||
));
|
||||
|
||||
return $result !== false ? $wpdb->insert_id : false;
|
||||
$table_type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener configuración de un componente
|
||||
* Get component configuration
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave específica (opcional)
|
||||
* @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto)
|
||||
* @return array|mixed Configuración completa o valor específico
|
||||
* @deprecated 2.0.0 Use GetComponentUseCase::execute() instead
|
||||
*
|
||||
* @param string $component_name Component name
|
||||
* @param string|null $config_key Specific configuration key (null for all)
|
||||
* @param string $table_type Table type (components or defaults)
|
||||
* @return mixed Configuration value(s) or null
|
||||
*/
|
||||
public function get_config($component_name, $config_key = null, $table_type = 'components') {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name($table_type);
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'ROITheme\Application\UseCases\GetComponent\GetComponentUseCase::execute()'
|
||||
);
|
||||
|
||||
if ($config_key !== null) {
|
||||
// Obtener un valor específico
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT config_value, data_type FROM $table_name
|
||||
WHERE component_name = %s AND config_key = %s",
|
||||
$component_name,
|
||||
$config_key
|
||||
));
|
||||
$this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args());
|
||||
|
||||
if ($row) {
|
||||
return $this->parse_value($row->config_value, $row->data_type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Obtener toda la configuración del componente
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT config_key, config_value, data_type FROM $table_name
|
||||
WHERE component_name = %s",
|
||||
$component_name
|
||||
));
|
||||
|
||||
$config = array();
|
||||
foreach ($rows as $row) {
|
||||
$config[$row->config_key] = $this->parse_value($row->config_value, $row->data_type);
|
||||
}
|
||||
|
||||
return $config;
|
||||
return $this->adapter->get_config($component_name, $config_key, $table_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsear valor según tipo de dato
|
||||
* Delete component configuration
|
||||
*
|
||||
* @param string $value Valor almacenado
|
||||
* @param string $data_type Tipo de dato
|
||||
* @return mixed Valor parseado
|
||||
*/
|
||||
private function parse_value($value, $data_type) {
|
||||
switch ($data_type) {
|
||||
case 'boolean':
|
||||
return (bool) $value;
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'json':
|
||||
return json_decode($value, true);
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar configuraciones de un componente
|
||||
* @deprecated 2.0.0 Use DeleteComponentUseCase::execute() instead
|
||||
*
|
||||
* @param string $component_name Nombre del componente
|
||||
* @param string $config_key Clave específica (opcional)
|
||||
* @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto)
|
||||
* @return bool Éxito de la operación
|
||||
* @param string $component_name Component name
|
||||
* @param string|null $config_key Specific configuration key (null for all component)
|
||||
* @param string $table_type Table type (components or defaults)
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function delete_config($component_name, $config_key = null, $table_type = 'components') {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name($table_type);
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'ROITheme\Application\UseCases\DeleteComponent\DeleteComponentUseCase::execute()'
|
||||
);
|
||||
|
||||
if ($config_key !== null) {
|
||||
return $wpdb->delete(
|
||||
$table_name,
|
||||
array(
|
||||
'component_name' => $component_name,
|
||||
'config_key' => $config_key
|
||||
),
|
||||
array('%s', '%s')
|
||||
) !== false;
|
||||
}
|
||||
$this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args());
|
||||
|
||||
// Eliminar todas las configuraciones del componente
|
||||
return $wpdb->delete(
|
||||
$table_name,
|
||||
array('component_name' => $component_name),
|
||||
array('%s')
|
||||
) !== false;
|
||||
// If deleting specific key, not supported in new architecture
|
||||
// Delete entire component instead
|
||||
return $this->adapter->delete_config($component_name, $table_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listar todos los componentes con configuraciones
|
||||
* List all components
|
||||
*
|
||||
* @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto)
|
||||
* @return array Lista de nombres de componentes
|
||||
* @deprecated 2.0.0 Use ComponentRepository::findAll() instead
|
||||
*
|
||||
* @param string $table_type Table type
|
||||
* @return array List of components
|
||||
*/
|
||||
public function list_components($table_type = 'components') {
|
||||
global $wpdb;
|
||||
$table_name = $this->get_table_name($table_type);
|
||||
_deprecated_function(
|
||||
__FUNCTION__,
|
||||
'2.0.0',
|
||||
'ROITheme\Domain\Contracts\ComponentRepositoryInterface::findAll()'
|
||||
);
|
||||
|
||||
return $wpdb->get_col(
|
||||
"SELECT DISTINCT component_name FROM $table_name ORDER BY component_name"
|
||||
$this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args());
|
||||
|
||||
global $wpdb;
|
||||
$table = $this->get_table_name($table_type);
|
||||
|
||||
$query = "SELECT DISTINCT component_name FROM $table ORDER BY component_name ASC";
|
||||
$components = $wpdb->get_col($query);
|
||||
|
||||
return $components ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get legacy adapter
|
||||
*
|
||||
* @return \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter
|
||||
*/
|
||||
private function getLegacyAdapter() {
|
||||
if (!class_exists('ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter')) {
|
||||
require_once get_template_directory() . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
return new \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log deprecation usage
|
||||
*
|
||||
* @param string $class Class name
|
||||
* @param string $method Method name
|
||||
* @param array $args Arguments
|
||||
*/
|
||||
private function logDeprecation($class, $method, $args = array()) {
|
||||
if (!class_exists('ROITheme\Infrastructure\Logging\DeprecationLogger')) {
|
||||
require_once get_template_directory() . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
$logger = \ROITheme\Infrastructure\Logging\DeprecationLogger::getInstance();
|
||||
$logger->log(
|
||||
$class,
|
||||
$method,
|
||||
$args,
|
||||
'See documentation for Clean Architecture migration',
|
||||
'2.0.0'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "roi/roi-theme",
|
||||
"description": "ROI Theme - Migración a Clean Architecture + POO",
|
||||
"type": "wordpress-theme",
|
||||
"license": "proprietary",
|
||||
"require": {
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"wp-coding-standards/wpcs": "^3.0",
|
||||
"mockery/mockery": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ROITheme\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ROITheme\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"phpcs": "phpcs --standard=WordPress src/",
|
||||
"phpcbf": "phpcbf --standard=WordPress src/"
|
||||
},
|
||||
"config": {
|
||||
"secure-http": true,
|
||||
"disable-tls": false,
|
||||
"preferred-install": "dist",
|
||||
"platform-check": false,
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
# Arquitectura del Sistema - ROI Theme
|
||||
|
||||
## Diagrama de Capas
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ FRAMEWORKS & DRIVERS │
|
||||
│ (WordPress, MySQL) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ INFRASTRUCTURE LAYER │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Repositories │ │ Controllers │ │ Services │ │
|
||||
│ │ (WordPress) │ │ (AJAX) │ │ (Validation) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Use Cases │ │ DTOs │ │ Interfaces │ │
|
||||
│ │ (SaveComp) │ │ (Request) │ │ (Contracts) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Entities │ │Value Objects │ │ Interfaces │ │
|
||||
│ │ (Component) │ │(CompName) │ │ (Repository) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Reglas de Dependencia
|
||||
|
||||
La **Regla de Dependencia** dice:
|
||||
|
||||
> Las dependencias de código fuente deben apuntar **hacia adentro**, hacia políticas de más alto nivel.
|
||||
|
||||
- **Dominio** no depende de nadie
|
||||
- **Aplicación** depende solo de Dominio
|
||||
- **Infraestructura** depende de Aplicación y Dominio
|
||||
- **Frameworks** son un detalle de Infraestructura
|
||||
|
||||
## Capas Explicadas
|
||||
|
||||
### 1. Domain Layer (Capa de Dominio)
|
||||
|
||||
**Responsabilidad**: Lógica de negocio pura
|
||||
|
||||
**Contiene**:
|
||||
- Entidades (Component)
|
||||
- Value Objects (ComponentName, ComponentConfiguration)
|
||||
- Interfaces de repositorios
|
||||
- Excepciones de dominio
|
||||
|
||||
**NO contiene**:
|
||||
- Código de WordPress
|
||||
- Acceso a base de datos
|
||||
- Código de UI
|
||||
|
||||
### 2. Application Layer (Capa de Aplicación)
|
||||
|
||||
**Responsabilidad**: Casos de uso y orquestación
|
||||
|
||||
**Contiene**:
|
||||
- Use Cases (SaveComponent, GetComponent)
|
||||
- DTOs (Request/Response objects)
|
||||
- Interfaces de servicios
|
||||
|
||||
**NO contiene**:
|
||||
- Lógica de negocio (eso va en Dominio)
|
||||
- Detalles de implementación (eso va en Infraestructura)
|
||||
|
||||
### 3. Infrastructure Layer (Capa de Infraestructura)
|
||||
|
||||
**Responsabilidad**: Implementación de detalles técnicos
|
||||
|
||||
**Contiene**:
|
||||
- Repositorios que usan WordPress/MySQL
|
||||
- Controllers (AJAX, REST)
|
||||
- Servicios (Validation, Cache)
|
||||
- Adaptadores de frameworks
|
||||
|
||||
**Depende de**:
|
||||
- Application Layer (usa Use Cases)
|
||||
- Domain Layer (implementa interfaces)
|
||||
- WordPress
|
||||
|
||||
## Ventajas de esta Arquitectura
|
||||
|
||||
1. **Testeable**: Fácil de testear sin WordPress
|
||||
2. **Mantenible**: Separación clara de responsabilidades
|
||||
3. **Escalable**: Fácil agregar nuevos features
|
||||
4. **Independiente**: El dominio no depende de frameworks
|
||||
5. **Flexible**: Fácil cambiar implementaciones
|
||||
@@ -1,264 +0,0 @@
|
||||
# Fase 1: Estructura Base y DI Container - COMPLETADO ✓
|
||||
|
||||
**Fecha de completitud**: 2025-01-17
|
||||
**Duración**: Según plan (5 días estimados)
|
||||
**Validación**: 48/48 checks pasados (100%)
|
||||
|
||||
---
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
La Fase 1 de la migración a Clean Architecture + POO se ha completado exitosamente con **100% de validaciones pasadas**. Se ha establecido la estructura base completa del proyecto siguiendo los principios de Clean Architecture y Domain-Driven Design.
|
||||
|
||||
---
|
||||
|
||||
## Tareas Completadas
|
||||
|
||||
### 1.1 ✓ Estructura Completa de Carpetas Clean Architecture
|
||||
|
||||
Creadas **28 carpetas** siguiendo la arquitectura de 4 capas:
|
||||
|
||||
#### Domain Layer
|
||||
- `src/Domain/Component/` - Entidad principal
|
||||
- `src/Domain/Component/ValueObjects/` - Value Objects
|
||||
- `src/Domain/Component/Exceptions/` - Excepciones de dominio
|
||||
- `src/Domain/Shared/ValueObjects/` - Value Objects compartidos
|
||||
|
||||
#### Application Layer
|
||||
- `src/Application/UseCases/SaveComponent/` - Caso de uso: Guardar componente
|
||||
- `src/Application/UseCases/GetComponent/` - Caso de uso: Obtener componente
|
||||
- `src/Application/UseCases/DeleteComponent/` - Caso de uso: Eliminar componente
|
||||
- `src/Application/UseCases/SyncSchema/` - Caso de uso: Sincronizar esquema
|
||||
- `src/Application/DTO/` - Data Transfer Objects
|
||||
- `src/Application/Contracts/` - Interfaces de servicios
|
||||
|
||||
#### Infrastructure Layer
|
||||
- `src/Infrastructure/Persistence/WordPress/Repositories/` - Repositorios
|
||||
- `src/Infrastructure/API/WordPress/` - Controllers AJAX/REST
|
||||
- `src/Infrastructure/Services/` - Servicios de infraestructura
|
||||
- `src/Infrastructure/DI/` - Dependency Injection Container
|
||||
- `src/Infrastructure/Facades/` - Facades (patrón)
|
||||
- `src/Infrastructure/Presentation/Public/Renderers/` - Renderers públicos
|
||||
- `src/Infrastructure/Presentation/Admin/FormBuilders/` - Form builders admin
|
||||
- `src/Infrastructure/UI/Assets/` - Assets CSS/JS
|
||||
- `src/Infrastructure/UI/Views/` - Vistas/Templates
|
||||
|
||||
#### Test Structure
|
||||
- `tests/Unit/Domain/Component/` - Tests unitarios de dominio
|
||||
- `tests/Unit/Application/UseCases/` - Tests de casos de uso
|
||||
- `tests/Unit/Infrastructure/Persistence/` - Tests de persistencia
|
||||
- `tests/Unit/Infrastructure/Services/` - Tests de servicios
|
||||
- `tests/Integration/` - Tests de integración
|
||||
- `tests/E2E/` - Tests end-to-end
|
||||
|
||||
#### Otros
|
||||
- `schemas/` - Esquemas de base de datos
|
||||
- `templates/admin/` - Templates de administración
|
||||
- `templates/public/` - Templates públicos
|
||||
|
||||
---
|
||||
|
||||
### 1.2 ✓ Composer con PSR-4 Autoloading
|
||||
|
||||
**Archivo**: `composer.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ROITheme\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ROITheme\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Estado**:
|
||||
- ✓ Autoloader optimizado generado
|
||||
- ✓ 1147 clases cargadas
|
||||
- ✓ Funcionamiento verificado
|
||||
|
||||
---
|
||||
|
||||
### 1.3 ✓ DI Container Implementado
|
||||
|
||||
**Archivo**: `src/Infrastructure/DI/DIContainer.php`
|
||||
|
||||
**Características**:
|
||||
- ✓ Patrón Singleton implementado
|
||||
- ✓ Prevención de clonación (`__clone()` privado)
|
||||
- ✓ Prevención de deserialización (`__wakeup()` lanza excepción)
|
||||
- ✓ Registro de servicios con `set()`
|
||||
- ✓ Recuperación de servicios con `get()`
|
||||
- ✓ Verificación de existencia con `has()`
|
||||
- ✓ Getters específicos:
|
||||
- `getComponentRepository()`
|
||||
- `getValidationService()`
|
||||
- `getCacheService()`
|
||||
- ✓ Método `reset()` para testing
|
||||
|
||||
**Interfaces Creadas**:
|
||||
1. `src/Domain/Component/ComponentRepositoryInterface.php`
|
||||
2. `src/Application/Contracts/ValidationServiceInterface.php`
|
||||
3. `src/Application/Contracts/CacheServiceInterface.php`
|
||||
|
||||
**Entidades Creadas**:
|
||||
1. `src/Domain/Component/Component.php` (placeholder)
|
||||
|
||||
---
|
||||
|
||||
### 1.4 ✓ Bootstrap en functions.php
|
||||
|
||||
**Archivo**: `functions.php` (líneas 14-46)
|
||||
|
||||
**Implementado**:
|
||||
```php
|
||||
// Load Composer autoloader
|
||||
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
// Initialize DI Container
|
||||
use ROITheme\Infrastructure\DI\DIContainer;
|
||||
|
||||
/**
|
||||
* Helper function to access DI Container
|
||||
*/
|
||||
function roi_container(): DIContainer {
|
||||
return DIContainer::getInstance();
|
||||
}
|
||||
```
|
||||
|
||||
**Estado**: ✓ Funcionando correctamente
|
||||
|
||||
---
|
||||
|
||||
### 1.5 ✓ Tests Unitarios
|
||||
|
||||
**Archivo**: `tests/Unit/Infrastructure/DI/DIContainerTest.php`
|
||||
|
||||
**Tests Implementados**: 10 tests, 24 assertions
|
||||
|
||||
1. ✓ `it_should_return_singleton_instance()` - Verifica patrón Singleton
|
||||
2. ✓ `it_should_prevent_cloning()` - Prevención de clonación
|
||||
3. ✓ `it_should_prevent_unserialization()` - Prevención de deserialización
|
||||
4. ✓ `it_should_register_and_retrieve_service()` - Registro/recuperación
|
||||
5. ✓ `it_should_return_null_for_non_existent_service()` - Servicio inexistente
|
||||
6. ✓ `it_should_throw_exception_for_unimplemented_component_repository()` - Placeholder
|
||||
7. ✓ `it_should_throw_exception_for_unimplemented_validation_service()` - Placeholder
|
||||
8. ✓ `it_should_throw_exception_for_unimplemented_cache_service()` - Placeholder
|
||||
9. ✓ `it_should_reset_singleton_instance()` - Reset para testing
|
||||
10. ✓ `it_should_manage_multiple_services()` - Múltiples servicios
|
||||
|
||||
**Resultado Total**:
|
||||
- Tests: 13 (10 DIContainer + 3 Example)
|
||||
- Assertions: 28
|
||||
- Warnings: 1 (deprecation notice PHPUnit 10)
|
||||
- **Estado**: TODOS PASANDO ✓
|
||||
|
||||
---
|
||||
|
||||
### 1.6 ✓ Validación Final
|
||||
|
||||
**Script**: `scripts/validate-phase-1.php`
|
||||
|
||||
**Resultado**: **48/48 validaciones pasadas (100%)**
|
||||
|
||||
**Categorías Validadas**:
|
||||
1. ✓ Estructura de carpetas (28 checks)
|
||||
2. ✓ Composer y autoloader (3 checks)
|
||||
3. ✓ DI Container (6 checks)
|
||||
4. ✓ Interfaces (4 checks)
|
||||
5. ✓ Bootstrap (4 checks)
|
||||
6. ✓ Tests unitarios (3 checks)
|
||||
|
||||
---
|
||||
|
||||
## Git
|
||||
|
||||
**Branch**: `migration/clean-architecture`
|
||||
**Commit**: `de5ff` - Fase 1: Estructura Base y DI Container - Clean Architecture
|
||||
**Tag**: `v1.0.0`
|
||||
|
||||
**Estadísticas del Commit**:
|
||||
- 149 archivos modificados
|
||||
- 3,187 inserciones (+)
|
||||
- 9,554 eliminaciones (-)
|
||||
- Limpieza de archivos legacy y documentación obsoleta
|
||||
|
||||
---
|
||||
|
||||
## Archivos Clave Creados
|
||||
|
||||
### Código de Producción
|
||||
1. `src/Infrastructure/DI/DIContainer.php` - DI Container (Singleton)
|
||||
2. `src/Domain/Component/Component.php` - Entidad Component (placeholder)
|
||||
3. `src/Domain/Component/ComponentRepositoryInterface.php` - Interfaz de repositorio
|
||||
4. `src/Application/Contracts/ValidationServiceInterface.php` - Interfaz de validación
|
||||
5. `src/Application/Contracts/CacheServiceInterface.php` - Interfaz de cache
|
||||
|
||||
### Tests
|
||||
6. `tests/Unit/Infrastructure/DI/DIContainerTest.php` - Tests del DI Container
|
||||
|
||||
### Scripts y Documentación
|
||||
7. `scripts/validate-phase-1.php` - Script de validación automatizado
|
||||
8. `docs/ARCHITECTURE.md` - Documentación de arquitectura
|
||||
9. `docs/GIT-BRANCHING-STRATEGY.md` - Estrategia de branching
|
||||
10. `src/Domain/README.md` - Documentación capa Domain
|
||||
11. `src/Application/README.md` - Documentación capa Application
|
||||
12. `src/Infrastructure/README.md` - Documentación capa Infrastructure
|
||||
|
||||
---
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
La arquitectura base está lista para la **Fase 2: Entidades y Value Objects**.
|
||||
|
||||
**Prerequisitos cumplidos para Fase 2**:
|
||||
- ✓ Estructura de carpetas completa
|
||||
- ✓ Autoloading PSR-4 funcionando
|
||||
- ✓ DI Container implementado y testeado
|
||||
- ✓ Bootstrap inicializando la arquitectura
|
||||
- ✓ Suite de tests configurada y pasando
|
||||
|
||||
**Fase 2 incluirá**:
|
||||
- Implementación completa de la entidad `Component`
|
||||
- Value Objects: `ComponentName`, `ComponentConfiguration`, etc.
|
||||
- Excepciones de dominio
|
||||
- Reglas de negocio puras
|
||||
- Tests unitarios de dominio
|
||||
|
||||
---
|
||||
|
||||
## Validación de Calidad
|
||||
|
||||
- ✓ Código siguiendo PSR-4
|
||||
- ✓ Type hints estrictos (`declare(strict_types=1)`)
|
||||
- ✓ DocBlocks completos
|
||||
- ✓ Tests con 100% de cobertura del DI Container
|
||||
- ✓ Zero dependencias de WordPress en Domain/Application
|
||||
- ✓ Dependency Inversion Principle aplicado
|
||||
- ✓ Single Responsibility Principle aplicado
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
1. **Placeholders**: Los servicios `ComponentRepository`, `ValidationService` y `CacheService` lanzarán `RuntimeException` hasta que sean implementados en Fase 5.
|
||||
|
||||
2. **Tests Warning**: Hay un warning de deprecación en PHPUnit sobre `expectError()`. Esto se resolverá cuando migremos a PHPUnit 10 en el futuro.
|
||||
|
||||
3. **Windows Compatibility**: El script de validación está optimizado para Windows con manejo especial de rutas (`DIRECTORY_SEPARATOR`).
|
||||
|
||||
4. **Autoloader Optimizado**: Se usa `composer dump-autoload -o` para generar autoloader optimizado con class map.
|
||||
|
||||
---
|
||||
|
||||
**Estado General**: ✅ FASE 1 COMPLETADA EXITOSAMENTE
|
||||
|
||||
**Validado por**: Script automatizado `scripts/validate-phase-1.php`
|
||||
**Fecha**: 2025-01-17
|
||||
@@ -1,103 +0,0 @@
|
||||
# Estrategia de Git Branching - ROI Theme Migration
|
||||
|
||||
## Modelo de Branching
|
||||
|
||||
Usaremos un modelo simplificado basado en **Git Flow** adaptado para esta migración.
|
||||
|
||||
## Estructura de Ramas
|
||||
|
||||
```
|
||||
main (producción)
|
||||
│
|
||||
└── migration/clean-architecture (rama principal de migración)
|
||||
│
|
||||
├── migration/fase-1-infrastructure
|
||||
├── migration/fase-2-database
|
||||
├── migration/fase-3-domain
|
||||
├── migration/fase-4-application
|
||||
├── migration/fase-5-infrastructure-layer
|
||||
├── migration/fase-6-components
|
||||
├── migration/fase-7-testing
|
||||
├── migration/fase-8-deprecation
|
||||
└── migration/fase-9-documentation
|
||||
```
|
||||
|
||||
## Reglas de Trabajo
|
||||
|
||||
### 1. Rama Principal de Migración
|
||||
|
||||
- **Nombre**: `migration/clean-architecture`
|
||||
- **Propósito**: Integrar todas las fases de la migración
|
||||
- **Protegida**: NO se pushea directamente a esta rama
|
||||
- **Base para**: Todas las ramas de fases individuales
|
||||
|
||||
### 2. Ramas de Fases
|
||||
|
||||
- **Naming**: `migration/fase-{número}-{nombre-descriptivo}`
|
||||
- **Flujo**:
|
||||
1. Crear desde `migration/clean-architecture`
|
||||
2. Desarrollar la fase completa
|
||||
3. Merge de vuelta a `migration/clean-architecture`
|
||||
4. Tag de versión (v{fase}.0.0)
|
||||
|
||||
## Workflow por Fase
|
||||
|
||||
```bash
|
||||
# 1. Asegurarse de estar en rama principal de migración
|
||||
git checkout migration/clean-architecture
|
||||
git pull origin migration/clean-architecture
|
||||
|
||||
# 2. Crear rama para nueva fase
|
||||
git checkout -b migration/fase-1-infrastructure
|
||||
|
||||
# 3. Trabajar en la fase
|
||||
git add .
|
||||
git commit -m "Descripción del cambio"
|
||||
|
||||
# 4. Al completar la fase
|
||||
git checkout migration/clean-architecture
|
||||
git merge --no-ff migration/fase-1-infrastructure -m "Merge Fase 1: Infrastructure Base"
|
||||
|
||||
# 5. Crear tag de versión
|
||||
git tag -a v1.0.0 -m "Fase 1 completada: Infrastructure Base"
|
||||
|
||||
# 6. Push (si hay repositorio remoto)
|
||||
git push origin migration/clean-architecture
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
## Commits
|
||||
|
||||
### Formato de Mensajes
|
||||
|
||||
```
|
||||
Fase {número}: {Título corto}
|
||||
|
||||
- Cambio 1
|
||||
- Cambio 2
|
||||
- Cambio 3
|
||||
```
|
||||
|
||||
## Tags
|
||||
|
||||
### Convención de Versionado
|
||||
|
||||
- **Fase completada**: `v{fase}.0.0`
|
||||
- Ejemplo: `v1.0.0` (Fase 1 completada)
|
||||
- **Correcciones menores**: `v{fase}.{minor}.0`
|
||||
- **Hotfixes**: `v{fase}.{minor}.{patch}`
|
||||
|
||||
## Checklist Pre-Commit
|
||||
|
||||
- [ ] Código sigue estándares (`composer phpcs`)
|
||||
- [ ] Tests pasan (`composer test`)
|
||||
- [ ] No hay archivos de backup incluidos
|
||||
- [ ] No hay credenciales hardcodeadas
|
||||
- [ ] Mensaje de commit es descriptivo
|
||||
|
||||
## Checklist Pre-Merge
|
||||
|
||||
- [ ] Todos los tests de la fase pasan
|
||||
- [ ] Código sigue estándares PHPCS
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] Tag de versión creado
|
||||
@@ -45,6 +45,69 @@ function roi_container(): DIContainer {
|
||||
* ========================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* ========================================================================
|
||||
* THEME DATABASE TABLES SETUP
|
||||
* ========================================================================
|
||||
*
|
||||
* Crea las tablas del tema cuando se activa.
|
||||
* Esto asegura que el tema sea portable y funcione en cualquier instalación WordPress.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Crear tablas del tema en la activación
|
||||
*
|
||||
* Este hook se ejecuta cuando el tema se activa en WordPress.
|
||||
* Crea las tablas necesarias si no existen.
|
||||
*
|
||||
* @since 1.0.19
|
||||
*/
|
||||
add_action('after_switch_theme', function() {
|
||||
global $wpdb;
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
// Tabla de components
|
||||
$table_components = $wpdb->prefix . 'roi_theme_components';
|
||||
$sql_components = "CREATE TABLE {$table_components} (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
configuration LONGTEXT NOT NULL,
|
||||
content LONGTEXT,
|
||||
visibility TEXT NOT NULL,
|
||||
is_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
schema_version VARCHAR(20) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY component_name (component_name),
|
||||
INDEX idx_enabled (is_enabled),
|
||||
INDEX idx_schema_version (schema_version)
|
||||
) {$charset_collate};";
|
||||
|
||||
// Tabla de defaults/schemas
|
||||
$table_defaults = $wpdb->prefix . 'roi_theme_defaults';
|
||||
$sql_defaults = "CREATE TABLE {$table_defaults} (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
default_schema LONGTEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY component_name (component_name)
|
||||
) {$charset_collate};";
|
||||
|
||||
// Crear/actualizar tablas
|
||||
dbDelta($sql_components);
|
||||
dbDelta($sql_defaults);
|
||||
|
||||
// Log en modo debug
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('ROI Theme: Database tables created/updated');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Theme Version
|
||||
*/
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
|
||||
Time: 132ms; Memory: 10MB
|
||||
|
||||
30
phpcs.xml
30
phpcs.xml
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="ROI Theme Coding Standards">
|
||||
<description>Estándares de código para ROI Theme</description>
|
||||
|
||||
<!-- Paths a analizar -->
|
||||
<file>src</file>
|
||||
|
||||
<!-- Paths a excluir -->
|
||||
<exclude-pattern>*/vendor/*</exclude-pattern>
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
<exclude-pattern>*/admin/*</exclude-pattern><!-- Legacy code -->
|
||||
|
||||
<!-- Usar estándar WordPress -->
|
||||
<rule ref="WordPress">
|
||||
<!-- Excluir reglas que no aplican para código nuevo -->
|
||||
<exclude name="WordPress.Files.FileName.InvalidClassFileName"/>
|
||||
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase"/>
|
||||
</rule>
|
||||
|
||||
<!-- Configuraciones adicionales -->
|
||||
<arg name="colors"/>
|
||||
<arg name="extensions" value="php"/>
|
||||
<arg name="parallel" value="8"/>
|
||||
|
||||
<!-- Mostrar progreso -->
|
||||
<arg value="sp"/>
|
||||
|
||||
<!-- PHP 8.0+ -->
|
||||
<config name="testVersion" value="8.0-"/>
|
||||
</ruleset>
|
||||
36
phpunit.xml
36
phpunit.xml
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
|
||||
bootstrap="tests/_bootstrap/bootstrap.php"
|
||||
colors="true"
|
||||
verbose="true"
|
||||
stopOnFailure="false"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Integration">
|
||||
<directory>tests/Integration</directory>
|
||||
</testsuite>
|
||||
<testsuite name="E2E">
|
||||
<directory>tests/E2E</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory>src/Infrastructure/UI/Views</directory>
|
||||
</exclude>
|
||||
</coverage>
|
||||
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
@@ -1,293 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Validation Script for Phase 1
|
||||
*
|
||||
* Validates that all Phase 1 tasks have been completed successfully.
|
||||
*
|
||||
* @package ROITheme
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Phase1Validator
|
||||
{
|
||||
private int $totalChecks = 0;
|
||||
private int $passedChecks = 0;
|
||||
private array $errors = [];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
echo "\n";
|
||||
echo "========================================\n";
|
||||
echo " VALIDACIÓN DE FASE 1 - ROI THEME \n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
$this->checkFolderStructure();
|
||||
$this->checkComposerAutoloader();
|
||||
$this->checkDIContainer();
|
||||
$this->checkInterfaces();
|
||||
$this->checkBootstrap();
|
||||
$this->checkTests();
|
||||
|
||||
$this->printResults();
|
||||
}
|
||||
|
||||
private function check(string $description, callable $test): void
|
||||
{
|
||||
$this->totalChecks++;
|
||||
|
||||
try {
|
||||
$result = $test();
|
||||
|
||||
if ($result) {
|
||||
echo "✓ {$description}\n";
|
||||
$this->passedChecks++;
|
||||
} else {
|
||||
echo "✗ {$description}\n";
|
||||
$this->errors[] = $description;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "✗ {$description} (Error: {$e->getMessage()})\n";
|
||||
$this->errors[] = $description . ': ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkFolderStructure(): void
|
||||
{
|
||||
echo "Validando estructura de carpetas...\n";
|
||||
|
||||
$requiredDirs = [
|
||||
'src/Domain/Component',
|
||||
'src/Domain/Component/ValueObjects',
|
||||
'src/Domain/Component/Exceptions',
|
||||
'src/Domain/Shared/ValueObjects',
|
||||
'src/Application/UseCases/SaveComponent',
|
||||
'src/Application/UseCases/GetComponent',
|
||||
'src/Application/UseCases/DeleteComponent',
|
||||
'src/Application/UseCases/SyncSchema',
|
||||
'src/Application/DTO',
|
||||
'src/Application/Contracts',
|
||||
'src/Infrastructure/Persistence/WordPress/Repositories',
|
||||
'src/Infrastructure/API/WordPress',
|
||||
'src/Infrastructure/Services',
|
||||
'src/Infrastructure/DI',
|
||||
'src/Infrastructure/Facades',
|
||||
'src/Infrastructure/Presentation/Public/Renderers',
|
||||
'src/Infrastructure/Presentation/Admin/FormBuilders',
|
||||
'src/Infrastructure/UI/Assets',
|
||||
'src/Infrastructure/UI/Views',
|
||||
'tests/Unit/Domain/Component',
|
||||
'tests/Unit/Application/UseCases',
|
||||
'tests/Unit/Infrastructure/Persistence',
|
||||
'tests/Unit/Infrastructure/Services',
|
||||
'tests/Integration',
|
||||
'tests/E2E',
|
||||
'schemas',
|
||||
'templates/admin',
|
||||
'templates/public',
|
||||
];
|
||||
|
||||
foreach ($requiredDirs as $dir) {
|
||||
$this->check(
|
||||
"Carpeta {$dir} existe",
|
||||
fn() => is_dir(__DIR__ . '/../' . $dir)
|
||||
);
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function checkComposerAutoloader(): void
|
||||
{
|
||||
echo "Validando Composer y autoloader...\n";
|
||||
|
||||
$this->check(
|
||||
'composer.json existe',
|
||||
fn() => file_exists(__DIR__ . '/../composer.json')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'vendor/autoload.php existe',
|
||||
fn() => file_exists(__DIR__ . '/../vendor/autoload.php')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'PSR-4 autoloading configurado',
|
||||
function () {
|
||||
$composer = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true);
|
||||
return isset($composer['autoload']['psr-4']['ROITheme\\']);
|
||||
}
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function checkDIContainer(): void
|
||||
{
|
||||
echo "Validando DI Container...\n";
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$this->check(
|
||||
'DIContainer class existe',
|
||||
fn() => class_exists('ROITheme\Infrastructure\DI\DIContainer')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainer es singleton',
|
||||
function () {
|
||||
$instance1 = \ROITheme\Infrastructure\DI\DIContainer::getInstance();
|
||||
$instance2 = \ROITheme\Infrastructure\DI\DIContainer::getInstance();
|
||||
return $instance1 === $instance2;
|
||||
}
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainer tiene método set()',
|
||||
fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'set')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainer tiene método get()',
|
||||
fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'get')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainer tiene método has()',
|
||||
fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'has')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainer tiene métodos de servicio',
|
||||
function () {
|
||||
return method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getComponentRepository')
|
||||
&& method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getValidationService')
|
||||
&& method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getCacheService');
|
||||
}
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function checkInterfaces(): void
|
||||
{
|
||||
echo "Validando interfaces...\n";
|
||||
|
||||
$this->check(
|
||||
'ComponentRepositoryInterface existe',
|
||||
fn() => interface_exists('ROITheme\Domain\Component\ComponentRepositoryInterface')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'ValidationServiceInterface existe',
|
||||
fn() => interface_exists('ROITheme\Application\Contracts\ValidationServiceInterface')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'CacheServiceInterface existe',
|
||||
fn() => interface_exists('ROITheme\Application\Contracts\CacheServiceInterface')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'Component entity existe',
|
||||
fn() => class_exists('ROITheme\Domain\Component\Component')
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function checkBootstrap(): void
|
||||
{
|
||||
echo "Validando bootstrap en functions.php...\n";
|
||||
|
||||
$functionsContent = file_get_contents(__DIR__ . '/../functions.php');
|
||||
|
||||
$this->check(
|
||||
'functions.php existe',
|
||||
fn() => file_exists(__DIR__ . '/../functions.php')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'functions.php carga Composer autoloader',
|
||||
fn() => strpos($functionsContent, "require_once __DIR__ . '/vendor/autoload.php'") !== false
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'functions.php importa DIContainer',
|
||||
fn() => strpos($functionsContent, 'use ROITheme\Infrastructure\DI\DIContainer') !== false
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'functions.php define roi_container()',
|
||||
fn() => strpos($functionsContent, 'function roi_container()') !== false
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function checkTests(): void
|
||||
{
|
||||
echo "Validando tests unitarios...\n";
|
||||
|
||||
$this->check(
|
||||
'phpunit.xml configurado',
|
||||
fn() => file_exists(__DIR__ . '/../phpunit.xml')
|
||||
);
|
||||
|
||||
$this->check(
|
||||
'DIContainerTest existe',
|
||||
fn() => file_exists(__DIR__ . '/../tests/Unit/Infrastructure/DI/DIContainerTest.php')
|
||||
);
|
||||
|
||||
// Run PHPUnit tests
|
||||
$baseDir = dirname(__DIR__);
|
||||
$phpunitPath = $baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'phpunit';
|
||||
|
||||
// For Windows compatibility
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$command = "cd /d \"$baseDir\" && \"$phpunitPath\" --testsuite Unit 2>&1";
|
||||
} else {
|
||||
$command = "cd \"$baseDir\" && \"$phpunitPath\" --testsuite Unit 2>&1";
|
||||
}
|
||||
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$this->check(
|
||||
'Tests unitarios pasan',
|
||||
fn() => $returnCode === 0
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
private function printResults(): void
|
||||
{
|
||||
echo "========================================\n";
|
||||
echo " RESULTADOS DE VALIDACIÓN\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
$percentage = $this->totalChecks > 0
|
||||
? round(($this->passedChecks / $this->totalChecks) * 100, 2)
|
||||
: 0;
|
||||
|
||||
echo "Checks pasados: {$this->passedChecks}/{$this->totalChecks} ({$percentage}%)\n\n";
|
||||
|
||||
if (count($this->errors) > 0) {
|
||||
echo "ERRORES ENCONTRADOS:\n";
|
||||
foreach ($this->errors as $error) {
|
||||
echo " - {$error}\n";
|
||||
}
|
||||
echo "\n";
|
||||
exit(1);
|
||||
} else {
|
||||
echo "✓ ¡FASE 1 COMPLETADA EXITOSAMENTE!\n\n";
|
||||
echo "Todas las validaciones pasaron correctamente.\n";
|
||||
echo "La arquitectura base está lista para la Fase 2.\n\n";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run validation
|
||||
$validator = new Phase1Validator();
|
||||
$validator->run();
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Application\Contracts;
|
||||
|
||||
/**
|
||||
* Cache Service Interface
|
||||
*
|
||||
* Defines the contract for caching services.
|
||||
* Implementations will provide caching mechanisms (transients, object cache, etc).
|
||||
*
|
||||
* @package ROITheme\Application\Contracts
|
||||
*/
|
||||
interface CacheServiceInterface
|
||||
{
|
||||
/**
|
||||
* Get cached value
|
||||
*
|
||||
* @param string $key Cache key
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key): mixed;
|
||||
|
||||
/**
|
||||
* Set cached value
|
||||
*
|
||||
* @param string $key Cache key
|
||||
* @param mixed $value Value to cache
|
||||
* @param int $expiration Expiration time in seconds
|
||||
* @return bool
|
||||
*/
|
||||
public function set(string $key, mixed $value, int $expiration = 0): bool;
|
||||
|
||||
/**
|
||||
* Delete cached value
|
||||
*
|
||||
* @param string $key Cache key
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(string $key): bool;
|
||||
|
||||
/**
|
||||
* Flush all cache
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Application\Contracts;
|
||||
|
||||
/**
|
||||
* Validation Service Interface
|
||||
*
|
||||
* Defines the contract for validation services.
|
||||
* Implementations will provide validation logic for various data types.
|
||||
*
|
||||
* @package ROITheme\Application\Contracts
|
||||
*/
|
||||
interface ValidationServiceInterface
|
||||
{
|
||||
/**
|
||||
* Validate data against rules
|
||||
*
|
||||
* @param array<string, mixed> $data Data to validate
|
||||
* @param array<string, mixed> $rules Validation rules
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $data, array $rules): bool;
|
||||
|
||||
/**
|
||||
* Get validation errors
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getErrors(): array;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
# Capa de Aplicación
|
||||
|
||||
## Propósito
|
||||
|
||||
La capa de Aplicación **orquesta** la lógica de negocio del dominio. Contiene los **Use Cases** (casos de uso) que coordinan las operaciones entre el dominio y la infraestructura.
|
||||
|
||||
## Principios
|
||||
|
||||
1. **Orquestación**: Coordina flujos de trabajo usando entidades de dominio
|
||||
2. **DTOs**: Usa Data Transfer Objects para comunicación entre capas
|
||||
3. **Contratos**: Define interfaces que la infraestructura implementará
|
||||
4. **Sin lógica de negocio**: Delega al dominio
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
Application/
|
||||
├── UseCases/ # Casos de uso
|
||||
│ ├── SaveComponent/
|
||||
│ │ ├── SaveComponentUseCase.php
|
||||
│ │ ├── SaveComponentRequest.php
|
||||
│ │ └── SaveComponentResponse.php
|
||||
│ ├── GetComponent/
|
||||
│ │ └── GetComponentUseCase.php
|
||||
│ └── DeleteComponent/
|
||||
│ └── DeleteComponentUseCase.php
|
||||
├── DTO/ # Data Transfer Objects
|
||||
│ ├── ComponentDTO.php
|
||||
│ └── ...
|
||||
└── Contracts/ # Interfaces de servicios
|
||||
├── ValidationServiceInterface.php
|
||||
├── CacheServiceInterface.php
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Reglas
|
||||
|
||||
1. **SÍ** usar dependencias via constructor injection
|
||||
2. **SÍ** usar interfaces del dominio
|
||||
3. **SÍ** usar DTOs para entrada/salida
|
||||
4. **NO** contener lógica de negocio (eso va en Dominio)
|
||||
5. **NO** depender de frameworks (solo via interfaces)
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Domain\Component;
|
||||
|
||||
/**
|
||||
* Component Entity
|
||||
*
|
||||
* Domain entity representing a component.
|
||||
* This is a placeholder that will be fully implemented in Phase 2.
|
||||
*
|
||||
* @package ROITheme\Domain\Component
|
||||
*/
|
||||
class Component
|
||||
{
|
||||
private int $id;
|
||||
private string $name;
|
||||
private array $configuration;
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
*
|
||||
* @param int $id Component ID
|
||||
* @param string $name Component name
|
||||
* @param array<string, mixed> $configuration Component configuration
|
||||
*/
|
||||
public function __construct(int $id, string $name, array $configuration = [])
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get component ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get component name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get component configuration
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Domain\Component;
|
||||
|
||||
/**
|
||||
* Component Repository Interface
|
||||
*
|
||||
* Defines the contract for component persistence.
|
||||
* This interface belongs to the Domain layer, but implementations will be in Infrastructure.
|
||||
*
|
||||
* @package ROITheme\Domain\Component
|
||||
*/
|
||||
interface ComponentRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Save a component
|
||||
*
|
||||
* @param Component $component Component to save
|
||||
* @return bool
|
||||
*/
|
||||
public function save(Component $component): bool;
|
||||
|
||||
/**
|
||||
* Find component by ID
|
||||
*
|
||||
* @param int $id Component ID
|
||||
* @return Component|null
|
||||
*/
|
||||
public function findById(int $id): ?Component;
|
||||
|
||||
/**
|
||||
* Find all components
|
||||
*
|
||||
* @return array<Component>
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* Delete a component
|
||||
*
|
||||
* @param int $id Component ID
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(int $id): bool;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
# Capa de Dominio
|
||||
|
||||
## Propósito
|
||||
|
||||
La capa de Dominio contiene la **lógica de negocio pura** del sistema. Es el corazón de la aplicación y no debe tener dependencias de frameworks, librerías externas, o capas superiores.
|
||||
|
||||
## Principios
|
||||
|
||||
1. **Independencia**: No depende de WordPress, base de datos, o cualquier framework
|
||||
2. **Inmutabilidad**: Los Value Objects son inmutables (readonly)
|
||||
3. **Validación**: Toda validación de reglas de negocio ocurre aquí
|
||||
4. **Pureza**: Funciones puras sin side effects
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
Domain/
|
||||
├── Component/ # Entidad principal: Component
|
||||
│ ├── Component.php # Entidad Component
|
||||
│ ├── ComponentRepositoryInterface.php # Contrato para repositorio
|
||||
│ ├── ValueObjects/ # Value Objects del componente
|
||||
│ │ ├── ComponentName.php
|
||||
│ │ ├── ComponentConfiguration.php
|
||||
│ │ ├── ComponentVisibility.php
|
||||
│ │ └── ComponentContent.php
|
||||
│ └── Exceptions/ # Excepciones de dominio
|
||||
│ ├── InvalidComponentException.php
|
||||
│ └── ComponentNotFoundException.php
|
||||
└── Shared/ # Value Objects compartidos
|
||||
└── ValueObjects/
|
||||
├── Email.php
|
||||
├── Url.php
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Reglas
|
||||
|
||||
1. **NO** usar código de WordPress (`global $wpdb`, `wp_*` functions)
|
||||
2. **NO** acceder a base de datos directamente
|
||||
3. **NO** usar `$_POST`, `$_GET`, `$_SESSION`
|
||||
4. **SÍ** definir interfaces para dependencias
|
||||
5. **SÍ** validar datos en constructores
|
||||
6. **SÍ** lanzar excepciones de dominio cuando algo viola reglas de negocio
|
||||
@@ -1,189 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Infrastructure\API\WordPress;
|
||||
|
||||
use ROITheme\Infrastructure\Persistence\WordPress\DatabaseMigrator;
|
||||
|
||||
/**
|
||||
* WP-CLI Command para Migración de Base de Datos
|
||||
*
|
||||
* Responsabilidad: Interfaz CLI para ejecutar migración de BD
|
||||
*
|
||||
* COMANDOS DISPONIBLES:
|
||||
* - wp roi-theme migrate : Ejecutar migración real
|
||||
* - wp roi-theme migrate --dry-run : Simular migración sin cambios
|
||||
* - wp roi-theme cleanup-backup : Eliminar tablas de backup
|
||||
*
|
||||
* FLUJO RECOMENDADO:
|
||||
* 1. Crear backup de BD manualmente
|
||||
* 2. Ejecutar: wp roi-theme migrate --dry-run (simulación)
|
||||
* 3. Ejecutar: wp roi-theme migrate (migración real)
|
||||
* 4. Validar funcionamiento por 7-30 días
|
||||
* 5. Ejecutar: wp roi-theme cleanup-backup
|
||||
*
|
||||
* USO:
|
||||
* ```bash
|
||||
* # Dry-run (simulación)
|
||||
* wp roi-theme migrate --dry-run
|
||||
*
|
||||
* # Migración real
|
||||
* wp roi-theme migrate
|
||||
*
|
||||
* # Limpiar backup (después de validar)
|
||||
* wp roi-theme cleanup-backup
|
||||
* ```
|
||||
*/
|
||||
final class MigrationCommand
|
||||
{
|
||||
/**
|
||||
* Ejecutar migración de BD legacy a Clean Architecture
|
||||
*
|
||||
* ## OPCIONES
|
||||
*
|
||||
* [--dry-run]
|
||||
* : Solo mostrar qué se haría sin ejecutar cambios reales
|
||||
*
|
||||
* ## EJEMPLOS
|
||||
*
|
||||
* # Simular migración
|
||||
* wp roi-theme migrate --dry-run
|
||||
*
|
||||
* # Ejecutar migración real
|
||||
* wp roi-theme migrate
|
||||
*
|
||||
* @param array $args Argumentos posicionales
|
||||
* @param array $assoc_args Argumentos asociativos (--flags)
|
||||
* @return void
|
||||
*/
|
||||
public function migrate(array $args, array $assoc_args): void
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$dry_run = isset($assoc_args['dry-run']);
|
||||
|
||||
if ($dry_run) {
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::line('🔍 MODO DRY-RUN: No se harán cambios reales');
|
||||
\WP_CLI::line('');
|
||||
}
|
||||
|
||||
\WP_CLI::line('🚀 Iniciando migración de base de datos...');
|
||||
\WP_CLI::line('');
|
||||
|
||||
$migrator = new DatabaseMigrator($wpdb);
|
||||
|
||||
if ($dry_run) {
|
||||
// Simular migración
|
||||
$this->simulateMigration($migrator);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ejecutar migración real
|
||||
$start_time = microtime(true);
|
||||
$result = $migrator->migrate();
|
||||
$end_time = microtime(true);
|
||||
$duration = round($end_time - $start_time, 2);
|
||||
|
||||
\WP_CLI::line('');
|
||||
|
||||
if ($result['success']) {
|
||||
\WP_CLI::success('✅ ' . $result['message']);
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::line('📊 Estadísticas de Migración:');
|
||||
\WP_CLI::line(' ├─ Components migrados: ' . ($result['stats']['components']['migrated'] ?? 0));
|
||||
\WP_CLI::line(' ├─ Defaults migrados: ' . ($result['stats']['defaults']['migrated'] ?? 0));
|
||||
\WP_CLI::line(' ├─ Tiempo de ejecución: ' . $duration . 's');
|
||||
\WP_CLI::line(' └─ Validación: ' . $result['stats']['validation']['message']);
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::line('⚠️ IMPORTANTE: Tablas legacy respaldadas con sufijo _backup');
|
||||
\WP_CLI::line(' Validar funcionamiento por 7-30 días antes de limpiar backup.');
|
||||
\WP_CLI::line(' Comando para limpiar: wp roi-theme cleanup-backup');
|
||||
\WP_CLI::line('');
|
||||
} else {
|
||||
\WP_CLI::error('❌ ' . $result['message']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpiar tablas de backup después de validar migración
|
||||
*
|
||||
* IMPORTANTE: Solo ejecutar después de validar que todo funciona correctamente
|
||||
*
|
||||
* ## EJEMPLOS
|
||||
*
|
||||
* wp roi-theme cleanup-backup
|
||||
*
|
||||
* @param array $args Argumentos posicionales
|
||||
* @param array $assoc_args Argumentos asociativos
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup_backup(array $args, array $assoc_args): void
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::confirm(
|
||||
'⚠️ ¿Estás seguro de eliminar las tablas de backup? Esta acción es IRREVERSIBLE.',
|
||||
$assoc_args
|
||||
);
|
||||
|
||||
$migrator = new DatabaseMigrator($wpdb);
|
||||
$migrator->cleanupBackup();
|
||||
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::success('✅ Tablas de backup eliminadas correctamente');
|
||||
\WP_CLI::line('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simular migración sin hacer cambios reales
|
||||
*
|
||||
* @param DatabaseMigrator $migrator Instancia del migrador
|
||||
* @return void
|
||||
*/
|
||||
private function simulateMigration(DatabaseMigrator $migrator): void
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
\WP_CLI::line('Verificando tablas legacy...');
|
||||
|
||||
// Verificar existencia de tablas
|
||||
$legacy_components = $wpdb->prefix . 'roi_theme_components';
|
||||
$legacy_defaults = $wpdb->prefix . 'roi_theme_components_defaults';
|
||||
|
||||
$components_exist = $wpdb->get_var("SHOW TABLES LIKE '{$legacy_components}'") === $legacy_components;
|
||||
$defaults_exist = $wpdb->get_var("SHOW TABLES LIKE '{$legacy_defaults}'") === $legacy_defaults;
|
||||
|
||||
if (!$components_exist || !$defaults_exist) {
|
||||
\WP_CLI::error('Tablas legacy no encontradas. No hay nada que migrar.');
|
||||
return;
|
||||
}
|
||||
|
||||
\WP_CLI::line('✓ Tablas legacy encontradas');
|
||||
|
||||
// Contar registros
|
||||
$components_count = $wpdb->get_var("SELECT COUNT(*) FROM {$legacy_components}");
|
||||
$defaults_count = $wpdb->get_var("SELECT COUNT(*) FROM {$legacy_defaults}");
|
||||
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::line('📊 Datos a migrar:');
|
||||
\WP_CLI::line(" ├─ Components: {$components_count} registros");
|
||||
\WP_CLI::line(" └─ Defaults: {$defaults_count} registros");
|
||||
\WP_CLI::line('');
|
||||
\WP_CLI::line('Operaciones que se ejecutarían:');
|
||||
\WP_CLI::line(' 1. Crear tablas v2 con nueva estructura (config_group)');
|
||||
\WP_CLI::line(' 2. Migrar ' . ($components_count + $defaults_count) . ' registros');
|
||||
\WP_CLI::line(' 3. Validar integridad de datos');
|
||||
\WP_CLI::line(' 4. Renombrar tablas (legacy → _backup, v2 → producción)');
|
||||
\WP_CLI::line('');
|
||||
|
||||
\WP_CLI::success('✅ Simulación completada. Ejecutar sin --dry-run para migración real.');
|
||||
\WP_CLI::line('');
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar comando WP-CLI
|
||||
if (defined('WP_CLI') && WP_CLI) {
|
||||
\WP_CLI::add_command('roi-theme', MigrationCommand::class);
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Infrastructure\DI;
|
||||
|
||||
use ROITheme\Domain\Component\ComponentRepositoryInterface;
|
||||
use ROITheme\Application\Contracts\ValidationServiceInterface;
|
||||
use ROITheme\Application\Contracts\CacheServiceInterface;
|
||||
|
||||
/**
|
||||
* Dependency Injection Container
|
||||
*
|
||||
* Singleton container that manages all service instances.
|
||||
* Provides centralized access to dependencies throughout the application.
|
||||
*
|
||||
* @package ROITheme\Infrastructure\DI
|
||||
*/
|
||||
final class DIContainer
|
||||
{
|
||||
private static ?DIContainer $instance = null;
|
||||
|
||||
/** @var array<string, object> */
|
||||
private array $services = [];
|
||||
|
||||
/**
|
||||
* Private constructor to prevent direct instantiation
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
// Initialize services here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent cloning
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent unserialization
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __wakeup(): void
|
||||
{
|
||||
throw new \Exception('Cannot unserialize singleton');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Component Repository
|
||||
*
|
||||
* @return ComponentRepositoryInterface
|
||||
*/
|
||||
public function getComponentRepository(): ComponentRepositoryInterface
|
||||
{
|
||||
if (!isset($this->services['component_repository'])) {
|
||||
// Placeholder - will be replaced in Phase 5
|
||||
throw new \RuntimeException('ComponentRepository not implemented yet. Will be available in Phase 5.');
|
||||
}
|
||||
|
||||
return $this->services['component_repository'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Validation Service
|
||||
*
|
||||
* @return ValidationServiceInterface
|
||||
*/
|
||||
public function getValidationService(): ValidationServiceInterface
|
||||
{
|
||||
if (!isset($this->services['validation_service'])) {
|
||||
// Placeholder - will be replaced in Phase 5
|
||||
throw new \RuntimeException('ValidationService not implemented yet. Will be available in Phase 5.');
|
||||
}
|
||||
|
||||
return $this->services['validation_service'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cache Service
|
||||
*
|
||||
* @return CacheServiceInterface
|
||||
*/
|
||||
public function getCacheService(): CacheServiceInterface
|
||||
{
|
||||
if (!isset($this->services['cache_service'])) {
|
||||
// Placeholder - will be replaced in Phase 5
|
||||
throw new \RuntimeException('CacheService not implemented yet. Will be available in Phase 5.');
|
||||
}
|
||||
|
||||
return $this->services['cache_service'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a service
|
||||
*
|
||||
* @param string $key Service identifier
|
||||
* @param object $service Service instance
|
||||
* @return void
|
||||
*/
|
||||
public function set(string $key, object $service): void
|
||||
{
|
||||
$this->services[$key] = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if service exists
|
||||
*
|
||||
* @param string $key Service identifier
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->services[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a service by key
|
||||
*
|
||||
* @param string $key Service identifier
|
||||
* @return object|null
|
||||
*/
|
||||
public function get(string $key): ?object
|
||||
{
|
||||
return $this->services[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset container (useful for testing)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Infrastructure\Persistence\WordPress;
|
||||
|
||||
/**
|
||||
* DatabaseMigrator - Migrador de Base de Datos Legacy a Clean Architecture
|
||||
*
|
||||
* Responsabilidad: Transformar estructura de BD legacy a nueva estructura
|
||||
*
|
||||
* ESTRATEGIA DE MIGRACIÓN:
|
||||
* 1. Crear tablas nuevas con sufijo _v2
|
||||
* 2. Migrar datos de legacy a nuevas tablas
|
||||
* 3. Validar integridad de datos migrados
|
||||
* 4. Renombrar tablas (legacy → _backup, v2 → producción)
|
||||
* 5. Mantener backup por 30 días para rollback
|
||||
*
|
||||
* TRANSFORMACIONES:
|
||||
* - Agregar columna config_group (inferida desde config_key)
|
||||
* - Renombrar version → schema_version
|
||||
* - Optimizar índices para consultas jerárquicas
|
||||
*
|
||||
* SEGURIDAD:
|
||||
* - Rollback automático si falla validación
|
||||
* - Backup de tablas legacy preservado
|
||||
* - Logging detallado de cada operación
|
||||
*
|
||||
* USO:
|
||||
* ```php
|
||||
* global $wpdb;
|
||||
* $migrator = new DatabaseMigrator($wpdb);
|
||||
* $result = $migrator->migrate();
|
||||
*
|
||||
* if ($result['success']) {
|
||||
* echo "Migración exitosa: " . $result['stats']['components']['migrated'] . " registros";
|
||||
* } else {
|
||||
* echo "Error: " . $result['message'];
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
final class DatabaseMigrator
|
||||
{
|
||||
/**
|
||||
* @var \wpdb Instancia de WordPress Database
|
||||
*/
|
||||
private \wpdb $wpdb;
|
||||
|
||||
/**
|
||||
* @var string Charset y Collation de la BD
|
||||
*/
|
||||
private string $charset_collate;
|
||||
|
||||
/**
|
||||
* @var array Log de operaciones ejecutadas
|
||||
*/
|
||||
private array $log = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \wpdb $wpdb Instancia de WordPress Database
|
||||
*/
|
||||
public function __construct(\wpdb $wpdb)
|
||||
{
|
||||
$this->wpdb = $wpdb;
|
||||
$this->charset_collate = $wpdb->get_charset_collate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecutar migración completa de BD
|
||||
*
|
||||
* @return array{success: bool, message: string, stats: array, log: array}
|
||||
*/
|
||||
public function migrate(): array
|
||||
{
|
||||
$this->log('🚀 Iniciando migración de base de datos');
|
||||
|
||||
try {
|
||||
// 1. Verificar que tablas legacy existen
|
||||
if (!$this->legacyTablesExist()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Tablas legacy no encontradas. No hay nada que migrar.',
|
||||
'stats' => [],
|
||||
'log' => $this->log
|
||||
];
|
||||
}
|
||||
|
||||
$this->log('✓ Tablas legacy encontradas');
|
||||
|
||||
// 2. Crear tablas v2 con nueva estructura
|
||||
$this->createV2Tables();
|
||||
$this->log('✓ Tablas v2 creadas con nueva estructura');
|
||||
|
||||
// 3. Migrar datos de components
|
||||
$componentsStats = $this->migrateComponentsData();
|
||||
$this->log("✓ Components migrados: {$componentsStats['migrated']} registros");
|
||||
|
||||
// 4. Migrar datos de defaults
|
||||
$defaultsStats = $this->migrateDefaultsData();
|
||||
$this->log("✓ Defaults migrados: {$defaultsStats['migrated']} registros");
|
||||
|
||||
// 5. Validar integridad de datos
|
||||
$validation = $this->validateMigration();
|
||||
|
||||
if (!$validation['success']) {
|
||||
throw new \RuntimeException(
|
||||
'Validación de migración falló: ' . $validation['message']
|
||||
);
|
||||
}
|
||||
|
||||
$this->log('✓ Validación de integridad exitosa');
|
||||
|
||||
// 6. Hacer swap de tablas
|
||||
$this->swapTables();
|
||||
$this->log('✓ Swap de tablas completado (legacy → _backup, v2 → producción)');
|
||||
|
||||
// 7. Guardar metadata de migración
|
||||
update_option('roi_theme_migration_date', current_time('mysql'));
|
||||
update_option('roi_theme_migration_stats', [
|
||||
'components' => $componentsStats,
|
||||
'defaults' => $defaultsStats
|
||||
]);
|
||||
|
||||
$this->log('✅ Migración completada exitosamente');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Migración completada exitosamente',
|
||||
'stats' => [
|
||||
'components' => $componentsStats,
|
||||
'defaults' => $defaultsStats,
|
||||
'validation' => $validation
|
||||
],
|
||||
'log' => $this->log
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->log('❌ Error durante migración: ' . $e->getMessage());
|
||||
|
||||
// Rollback en caso de error
|
||||
$this->rollback();
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Error durante migración: ' . $e->getMessage(),
|
||||
'stats' => [],
|
||||
'log' => $this->log
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar si las tablas legacy existen
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function legacyTablesExist(): bool
|
||||
{
|
||||
$legacy_components = $this->wpdb->prefix . 'roi_theme_components';
|
||||
$legacy_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults';
|
||||
|
||||
$components_exist = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$legacy_components}'"
|
||||
) === $legacy_components;
|
||||
|
||||
$defaults_exist = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$legacy_defaults}'"
|
||||
) === $legacy_defaults;
|
||||
|
||||
return $components_exist && $defaults_exist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear tablas v2 con nueva estructura
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createV2Tables(): void
|
||||
{
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
|
||||
$components_v2 = $this->wpdb->prefix . 'roi_theme_components_v2';
|
||||
$defaults_v2 = $this->wpdb->prefix . 'roi_theme_components_defaults_v2';
|
||||
|
||||
// Estructura mejorada con config_group
|
||||
$table_structure = "
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
config_group VARCHAR(50) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL,
|
||||
config_value TEXT NOT NULL,
|
||||
data_type VARCHAR(20) NOT NULL DEFAULT 'string',
|
||||
schema_version VARCHAR(10) NOT NULL DEFAULT '1.0.0',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY component_config (component_name, config_group, config_key),
|
||||
INDEX idx_component (component_name),
|
||||
INDEX idx_group (component_name, config_group),
|
||||
INDEX idx_schema_version (component_name, schema_version),
|
||||
INDEX idx_config_key (config_key)
|
||||
";
|
||||
|
||||
$sql_components = "CREATE TABLE {$components_v2} ({$table_structure}) {$this->charset_collate};";
|
||||
$sql_defaults = "CREATE TABLE {$defaults_v2} ({$table_structure}) {$this->charset_collate};";
|
||||
|
||||
dbDelta($sql_components);
|
||||
dbDelta($sql_defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrar datos de components legacy a v2
|
||||
*
|
||||
* @return array{migrated: int, skipped: int, errors: array}
|
||||
*/
|
||||
private function migrateComponentsData(): array
|
||||
{
|
||||
$legacy_table = $this->wpdb->prefix . 'roi_theme_components';
|
||||
$v2_table = $this->wpdb->prefix . 'roi_theme_components_v2';
|
||||
|
||||
return $this->migrateTableData($legacy_table, $v2_table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrar datos de defaults legacy a v2
|
||||
*
|
||||
* @return array{migrated: int, skipped: int, errors: array}
|
||||
*/
|
||||
private function migrateDefaultsData(): array
|
||||
{
|
||||
$legacy_table = $this->wpdb->prefix . 'roi_theme_components_defaults';
|
||||
$v2_table = $this->wpdb->prefix . 'roi_theme_components_defaults_v2';
|
||||
|
||||
return $this->migrateTableData($legacy_table, $v2_table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrar datos de una tabla legacy a v2
|
||||
*
|
||||
* @param string $legacy_table Nombre de tabla legacy
|
||||
* @param string $v2_table Nombre de tabla v2
|
||||
* @return array{migrated: int, skipped: int, errors: array}
|
||||
*/
|
||||
private function migrateTableData(string $legacy_table, string $v2_table): array
|
||||
{
|
||||
// Obtener todos los registros legacy
|
||||
$legacy_rows = $this->wpdb->get_results(
|
||||
"SELECT * FROM {$legacy_table}",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if (empty($legacy_rows)) {
|
||||
return ['migrated' => 0, 'skipped' => 0, 'errors' => []];
|
||||
}
|
||||
|
||||
$migrated = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($legacy_rows as $row) {
|
||||
try {
|
||||
// Inferir config_group desde config_key
|
||||
$group = $this->inferGroupFromKey($row['config_key']);
|
||||
|
||||
// Preparar datos para inserción
|
||||
$data = [
|
||||
'component_name' => $row['component_name'],
|
||||
'config_group' => $group,
|
||||
'config_key' => $row['config_key'],
|
||||
'config_value' => $row['config_value'],
|
||||
'data_type' => $row['data_type'],
|
||||
'schema_version' => $row['version'] ?? '1.0.0',
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at']
|
||||
];
|
||||
|
||||
// Insertar en tabla v2
|
||||
$result = $this->wpdb->insert(
|
||||
$v2_table,
|
||||
$data,
|
||||
['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
$migrated++;
|
||||
} else {
|
||||
$skipped++;
|
||||
$errors[] = "Error migrando: {$row['component_name']}.{$row['config_key']}";
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$skipped++;
|
||||
$errors[] = "Excepción migrando {$row['component_name']}.{$row['config_key']}: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'migrated' => $migrated,
|
||||
'skipped' => $skipped,
|
||||
'errors' => $errors
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inferir grupo de configuración desde la clave
|
||||
*
|
||||
* HEURÍSTICA:
|
||||
* - enabled, visible_* → visibility
|
||||
* - message_*, cta_*, title_* → content
|
||||
* - *_color, *_height, *_width, *_size → styles
|
||||
* - Resto → general
|
||||
*
|
||||
* @param string $key Clave de configuración
|
||||
* @return string Grupo inferido
|
||||
*/
|
||||
private function inferGroupFromKey(string $key): string
|
||||
{
|
||||
// Visibility
|
||||
if (in_array($key, ['enabled', 'visible_desktop', 'visible_mobile', 'visible_tablet'], true)) {
|
||||
return 'visibility';
|
||||
}
|
||||
|
||||
// Content
|
||||
if (str_starts_with($key, 'message_') ||
|
||||
str_starts_with($key, 'cta_') ||
|
||||
str_starts_with($key, 'title_')) {
|
||||
return 'content';
|
||||
}
|
||||
|
||||
// Styles
|
||||
if (str_ends_with($key, '_color') ||
|
||||
str_ends_with($key, '_height') ||
|
||||
str_ends_with($key, '_width') ||
|
||||
str_ends_with($key, '_size') ||
|
||||
str_ends_with($key, '_font')) {
|
||||
return 'styles';
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar integridad de la migración
|
||||
*
|
||||
* @return array{success: bool, message: string, details: array}
|
||||
*/
|
||||
private function validateMigration(): array
|
||||
{
|
||||
$legacy_components = $this->wpdb->prefix . 'roi_theme_components';
|
||||
$legacy_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults';
|
||||
$v2_components = $this->wpdb->prefix . 'roi_theme_components_v2';
|
||||
$v2_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults_v2';
|
||||
|
||||
$details = [];
|
||||
|
||||
// 1. Validar cantidad de registros components
|
||||
$legacy_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$legacy_components}");
|
||||
$v2_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$v2_components}");
|
||||
|
||||
$details['components_count'] = [
|
||||
'legacy' => (int) $legacy_count,
|
||||
'v2' => (int) $v2_count,
|
||||
'match' => $legacy_count == $v2_count
|
||||
];
|
||||
|
||||
if ($legacy_count != $v2_count) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Mismatch en cantidad de registros components: legacy={$legacy_count}, v2={$v2_count}",
|
||||
'details' => $details
|
||||
];
|
||||
}
|
||||
|
||||
// 2. Validar cantidad de registros defaults
|
||||
$legacy_defaults_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$legacy_defaults}");
|
||||
$v2_defaults_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$v2_defaults}");
|
||||
|
||||
$details['defaults_count'] = [
|
||||
'legacy' => (int) $legacy_defaults_count,
|
||||
'v2' => (int) $v2_defaults_count,
|
||||
'match' => $legacy_defaults_count == $v2_defaults_count
|
||||
];
|
||||
|
||||
if ($legacy_defaults_count != $v2_defaults_count) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Mismatch en cantidad de registros defaults: legacy={$legacy_defaults_count}, v2={$v2_defaults_count}",
|
||||
'details' => $details
|
||||
];
|
||||
}
|
||||
|
||||
// 3. Verificar componentes únicos
|
||||
$legacy_component_names = $this->wpdb->get_col(
|
||||
"SELECT DISTINCT component_name FROM {$legacy_components}"
|
||||
);
|
||||
$v2_component_names = $this->wpdb->get_col(
|
||||
"SELECT DISTINCT component_name FROM {$v2_components}"
|
||||
);
|
||||
|
||||
$missing_components = array_diff($legacy_component_names, $v2_component_names);
|
||||
|
||||
if (count($missing_components) > 0) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Faltan componentes en v2: " . implode(', ', $missing_components),
|
||||
'details' => $details
|
||||
];
|
||||
}
|
||||
|
||||
// 4. Verificar grupos válidos
|
||||
$invalid_groups = $this->wpdb->get_col(
|
||||
"SELECT DISTINCT config_group FROM {$v2_components}
|
||||
WHERE config_group NOT IN ('visibility', 'content', 'styles', 'general')"
|
||||
);
|
||||
|
||||
if (count($invalid_groups) > 0) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Grupos inválidos encontrados: " . implode(', ', $invalid_groups),
|
||||
'details' => $details
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "Validación exitosa: {$legacy_count} components + {$legacy_defaults_count} defaults migrados correctamente",
|
||||
'details' => $details
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hacer swap de tablas (legacy → backup, v2 → producción)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function swapTables(): void
|
||||
{
|
||||
// Swap components
|
||||
$this->wpdb->query(
|
||||
"RENAME TABLE
|
||||
{$this->wpdb->prefix}roi_theme_components TO {$this->wpdb->prefix}roi_theme_components_backup,
|
||||
{$this->wpdb->prefix}roi_theme_components_v2 TO {$this->wpdb->prefix}roi_theme_components"
|
||||
);
|
||||
|
||||
// Swap defaults
|
||||
$this->wpdb->query(
|
||||
"RENAME TABLE
|
||||
{$this->wpdb->prefix}roi_theme_components_defaults TO {$this->wpdb->prefix}roi_theme_components_defaults_backup,
|
||||
{$this->wpdb->prefix}roi_theme_components_defaults_v2 TO {$this->wpdb->prefix}roi_theme_components_defaults"
|
||||
);
|
||||
|
||||
// Guardar timestamp de backup
|
||||
update_option('roi_theme_migration_backup_date', current_time('mysql'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback: Eliminar tablas v2 en caso de error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function rollback(): void
|
||||
{
|
||||
$this->log('⚠️ Ejecutando rollback...');
|
||||
|
||||
// Eliminar tablas v2 si existen
|
||||
$this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_v2");
|
||||
$this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_defaults_v2");
|
||||
|
||||
$this->log('✓ Rollback completado: tablas v2 eliminadas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpiar tablas de backup (ejecutar después de validar migración)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanupBackup(): void
|
||||
{
|
||||
$this->log('🗑️ Eliminando tablas de backup...');
|
||||
|
||||
$this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_backup");
|
||||
$this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_defaults_backup");
|
||||
|
||||
delete_option('roi_theme_migration_backup_date');
|
||||
|
||||
$this->log('✓ Tablas de backup eliminadas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Agregar entrada al log
|
||||
*
|
||||
* @param string $message Mensaje a registrar
|
||||
* @return void
|
||||
*/
|
||||
private function log(string $message): void
|
||||
{
|
||||
$timestamp = current_time('Y-m-d H:i:s');
|
||||
$entry = "[{$timestamp}] {$message}";
|
||||
|
||||
$this->log[] = $entry;
|
||||
error_log('ROI Theme Migration: ' . $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener log completo de operaciones
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLog(): array
|
||||
{
|
||||
return $this->log;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
# Capa de Infraestructura
|
||||
|
||||
## Propósito
|
||||
|
||||
La capa de Infraestructura **implementa** los detalles técnicos: persistencia, APIs, servicios externos, frameworks. Es la capa más externa y depende de las capas internas.
|
||||
|
||||
## Principios
|
||||
|
||||
1. **Implementación**: Implementa interfaces definidas en Dominio/Aplicación
|
||||
2. **Frameworks**: Aquí vive el código específico de WordPress
|
||||
3. **Adaptación**: Adapta datos entre formatos externos e internos
|
||||
4. **Dependencia Invertida**: Depende de abstracciones del dominio
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
Infrastructure/
|
||||
├── Persistence/ # Acceso a datos
|
||||
│ └── WordPress/
|
||||
│ ├── WordPressComponentRepository.php
|
||||
│ └── DatabaseMigrator.php
|
||||
├── API/ # Controllers/Endpoints
|
||||
│ └── WordPress/
|
||||
│ ├── AjaxController.php
|
||||
│ └── RestController.php
|
||||
├── Services/ # Servicios de infraestructura
|
||||
│ ├── WordPressValidationService.php
|
||||
│ ├── WordPressCacheService.php
|
||||
│ └── WordPressComponentFacade.php
|
||||
├── DI/ # Dependency Injection
|
||||
│ └── DIContainer.php
|
||||
└── UI/ # Vistas y assets
|
||||
├── Views/
|
||||
│ └── components/
|
||||
└── Assets/
|
||||
├── css/
|
||||
└── js/
|
||||
```
|
||||
|
||||
## Reglas
|
||||
|
||||
1. **SÍ** usar código de WordPress aquí
|
||||
2. **SÍ** implementar interfaces del dominio/aplicación
|
||||
3. **SÍ** adaptar datos entre WordPress y entidades
|
||||
4. **NO** exponer detalles de implementación hacia arriba
|
||||
5. **NO** contener lógica de negocio
|
||||
@@ -1,365 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Tests\Integration\Infrastructure;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ROITheme\Infrastructure\Persistence\WordPress\DatabaseMigrator;
|
||||
|
||||
/**
|
||||
* Tests de Integración para DatabaseMigrator
|
||||
*
|
||||
* IMPORTANTE: Estos tests modifican la base de datos
|
||||
* Solo ejecutar en entorno de testing
|
||||
*/
|
||||
class DatabaseMigratorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \wpdb
|
||||
*/
|
||||
private \wpdb $wpdb;
|
||||
|
||||
/**
|
||||
* @var DatabaseMigrator
|
||||
*/
|
||||
private DatabaseMigrator $migrator;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $prefix;
|
||||
|
||||
/**
|
||||
* Setup antes de cada test
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (!defined('ABSPATH')) {
|
||||
define('ABSPATH', dirname(__DIR__, 5) . '/');
|
||||
}
|
||||
|
||||
if (!function_exists('update_option')) {
|
||||
function update_option($option, $value) { return true; }
|
||||
}
|
||||
|
||||
if (!function_exists('delete_option')) {
|
||||
function delete_option($option) { return true; }
|
||||
}
|
||||
|
||||
if (!function_exists('current_time')) {
|
||||
function current_time($type) { return date('Y-m-d H:i:s'); }
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if (!isset($wpdb)) {
|
||||
$this->markTestSkipped('WordPress not loaded - skipping integration tests');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
$this->prefix = $wpdb->prefix;
|
||||
$this->migrator = new DatabaseMigrator($wpdb);
|
||||
|
||||
// Limpiar tablas anteriores
|
||||
$this->cleanupTables();
|
||||
|
||||
// Crear tablas legacy con datos de prueba
|
||||
$this->createLegacyTables();
|
||||
$this->seedLegacyData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Teardown después de cada test
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->cleanupTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: La migración crea tablas v2 correctamente
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_creates_v2_tables(): void
|
||||
{
|
||||
$result = $this->migrator->migrate();
|
||||
|
||||
$this->assertTrue($result['success'], $result['message']);
|
||||
|
||||
// Verificar que tabla components existe
|
||||
$components_table = $this->prefix . 'roi_theme_components';
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$components_table}'"
|
||||
) === $components_table;
|
||||
|
||||
$this->assertTrue($table_exists, 'Tabla components no existe después de migración');
|
||||
|
||||
// Verificar que tabla defaults existe
|
||||
$defaults_table = $this->prefix . 'roi_theme_components_defaults';
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$defaults_table}'"
|
||||
) === $defaults_table;
|
||||
|
||||
$this->assertTrue($table_exists, 'Tabla defaults no existe después de migración');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: La migración preserva la cantidad de registros
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_preserves_record_count(): void
|
||||
{
|
||||
// Contar antes de migración
|
||||
$legacy_count = $this->wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$this->prefix}roi_theme_components"
|
||||
);
|
||||
|
||||
// Ejecutar migración
|
||||
$result = $this->migrator->migrate();
|
||||
$this->assertTrue($result['success']);
|
||||
|
||||
// Contar después de migración
|
||||
$v2_count = $this->wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$this->prefix}roi_theme_components"
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$legacy_count,
|
||||
$v2_count,
|
||||
"Cantidad de registros no coincide: legacy={$legacy_count}, v2={$v2_count}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: La migración infiere grupos correctamente
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_infers_config_groups_correctly(): void
|
||||
{
|
||||
$result = $this->migrator->migrate();
|
||||
$this->assertTrue($result['success']);
|
||||
|
||||
// Verificar que "enabled" se migró a grupo "visibility"
|
||||
$group = $this->wpdb->get_var(
|
||||
"SELECT config_group FROM {$this->prefix}roi_theme_components
|
||||
WHERE config_key = 'enabled' LIMIT 1"
|
||||
);
|
||||
|
||||
$this->assertEquals('visibility', $group);
|
||||
|
||||
// Verificar que "message_text" se migró a grupo "content"
|
||||
$group = $this->wpdb->get_var(
|
||||
"SELECT config_group FROM {$this->prefix}roi_theme_components
|
||||
WHERE config_key = 'message_text' LIMIT 1"
|
||||
);
|
||||
|
||||
$this->assertEquals('content', $group);
|
||||
|
||||
// Verificar que "background_color" se migró a grupo "styles"
|
||||
$group = $this->wpdb->get_var(
|
||||
"SELECT config_group FROM {$this->prefix}roi_theme_components
|
||||
WHERE config_key = 'background_color' LIMIT 1"
|
||||
);
|
||||
|
||||
$this->assertEquals('styles', $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: La migración crea backup de tablas legacy
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_creates_backup_tables(): void
|
||||
{
|
||||
$result = $this->migrator->migrate();
|
||||
$this->assertTrue($result['success']);
|
||||
|
||||
// Verificar que tabla backup existe
|
||||
$backup_table = $this->prefix . 'roi_theme_components_backup';
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$backup_table}'"
|
||||
) === $backup_table;
|
||||
|
||||
$this->assertTrue($table_exists, 'Tabla backup no fue creada');
|
||||
|
||||
// Verificar que backup tiene datos
|
||||
$backup_count = $this->wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$backup_table}"
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(0, $backup_count, 'Tabla backup está vacía');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Rollback elimina tablas v2 en caso de error
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_rolls_back_on_error(): void
|
||||
{
|
||||
// Crear escenario de error: tabla legacy vacía después de crear v2
|
||||
$this->wpdb->query("DELETE FROM {$this->prefix}roi_theme_components");
|
||||
|
||||
$result = $this->migrator->migrate();
|
||||
|
||||
// La migración debe fallar por mismatch de conteo
|
||||
$this->assertFalse($result['success']);
|
||||
|
||||
// Verificar que tablas v2 fueron eliminadas
|
||||
$v2_table = $this->prefix . 'roi_theme_components_v2';
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$v2_table}'"
|
||||
) === $v2_table;
|
||||
|
||||
$this->assertFalse($table_exists, 'Tabla v2 no fue eliminada en rollback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: cleanup_backup elimina tablas de respaldo
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function it_removes_backup_tables(): void
|
||||
{
|
||||
// Ejecutar migración
|
||||
$result = $this->migrator->migrate();
|
||||
$this->assertTrue($result['success']);
|
||||
|
||||
// Verificar que backup existe
|
||||
$backup_table = $this->prefix . 'roi_theme_components_backup';
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$backup_table}'"
|
||||
) === $backup_table;
|
||||
$this->assertTrue($table_exists);
|
||||
|
||||
// Ejecutar cleanup
|
||||
$this->migrator->cleanupBackup();
|
||||
|
||||
// Verificar que backup fue eliminado
|
||||
$table_exists = $this->wpdb->get_var(
|
||||
"SHOW TABLES LIKE '{$backup_table}'"
|
||||
) === $backup_table;
|
||||
|
||||
$this->assertFalse($table_exists, 'Tabla backup no fue eliminada');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Crear tablas legacy para testing
|
||||
*/
|
||||
private function createLegacyTables(): void
|
||||
{
|
||||
if (!defined('ABSPATH')) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
|
||||
$charset_collate = $this->wpdb->get_charset_collate();
|
||||
|
||||
$components_sql = "CREATE TABLE {$this->prefix}roi_theme_components (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL,
|
||||
config_value TEXT NOT NULL,
|
||||
data_type VARCHAR(20) NOT NULL DEFAULT 'string',
|
||||
version VARCHAR(10) NOT NULL DEFAULT '1.0.0',
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY component_config (component_name, config_key)
|
||||
) {$charset_collate};";
|
||||
|
||||
$defaults_sql = "CREATE TABLE {$this->prefix}roi_theme_components_defaults (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
component_name VARCHAR(50) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL,
|
||||
config_value TEXT NOT NULL,
|
||||
data_type VARCHAR(20) NOT NULL DEFAULT 'string',
|
||||
version VARCHAR(10) NOT NULL DEFAULT '1.0.0',
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY component_config (component_name, config_key)
|
||||
) {$charset_collate};";
|
||||
|
||||
dbDelta($components_sql);
|
||||
dbDelta($defaults_sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Insertar datos de prueba en tablas legacy
|
||||
*/
|
||||
private function seedLegacyData(): void
|
||||
{
|
||||
$timestamp = current_time('mysql');
|
||||
|
||||
// Components
|
||||
$components_data = [
|
||||
['top_bar', 'enabled', '1', 'boolean'],
|
||||
['top_bar', 'message_text', 'Welcome!', 'string'],
|
||||
['top_bar', 'background_color', '#000000', 'string'],
|
||||
['footer', 'enabled', '1', 'boolean'],
|
||||
['footer', 'cta_url', 'https://example.com', 'string'],
|
||||
['footer', 'cta_text', 'Click here', 'string'],
|
||||
];
|
||||
|
||||
foreach ($components_data as $data) {
|
||||
$this->wpdb->insert(
|
||||
$this->prefix . 'roi_theme_components',
|
||||
[
|
||||
'component_name' => $data[0],
|
||||
'config_key' => $data[1],
|
||||
'config_value' => $data[2],
|
||||
'data_type' => $data[3],
|
||||
'version' => '1.0.0',
|
||||
'created_at' => $timestamp,
|
||||
'updated_at' => $timestamp
|
||||
],
|
||||
['%s', '%s', '%s', '%s', '%s', '%s', '%s']
|
||||
);
|
||||
}
|
||||
|
||||
// Defaults (misma estructura)
|
||||
$this->wpdb->insert(
|
||||
$this->prefix . 'roi_theme_components_defaults',
|
||||
[
|
||||
'component_name' => 'top_bar',
|
||||
'config_key' => 'enabled',
|
||||
'config_value' => '1',
|
||||
'data_type' => 'boolean',
|
||||
'version' => '1.0.0',
|
||||
'created_at' => $timestamp,
|
||||
'updated_at' => $timestamp
|
||||
],
|
||||
['%s', '%s', '%s', '%s', '%s', '%s', '%s']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Limpiar todas las tablas del test
|
||||
*/
|
||||
private function cleanupTables(): void
|
||||
{
|
||||
$tables = [
|
||||
'roi_theme_components',
|
||||
'roi_theme_components_defaults',
|
||||
'roi_theme_components_v2',
|
||||
'roi_theme_components_defaults_v2',
|
||||
'roi_theme_components_backup',
|
||||
'roi_theme_components_defaults_backup'
|
||||
];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$this->wpdb->query("DROP TABLE IF EXISTS {$this->prefix}{$table}");
|
||||
}
|
||||
|
||||
// Limpiar opciones
|
||||
delete_option('roi_theme_migration_date');
|
||||
delete_option('roi_theme_migration_backup_date');
|
||||
delete_option('roi_theme_migration_stats');
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ROITheme\Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test de ejemplo para verificar que PHPUnit funciona
|
||||
*/
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
public function testPhpUnitIsWorking(): void
|
||||
{
|
||||
$this->assertTrue(true, 'PHPUnit está funcionando correctamente');
|
||||
}
|
||||
|
||||
public function testPhpVersion(): void
|
||||
{
|
||||
$version = PHP_VERSION;
|
||||
$this->assertGreaterThanOrEqual('8.0.0', $version, 'PHP debe ser versión 8.0 o superior');
|
||||
}
|
||||
|
||||
public function testComposerAutoloadIsLoaded(): void
|
||||
{
|
||||
$this->assertTrue(
|
||||
class_exists('PHPUnit\Framework\TestCase'),
|
||||
'Composer autoload está funcionando'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Tests\Unit\Infrastructure\DI;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ROITheme\Infrastructure\DI\DIContainer;
|
||||
use RuntimeException;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* DI Container Test
|
||||
*
|
||||
* Tests for the Dependency Injection Container.
|
||||
*
|
||||
* @package ROITheme\Tests\Unit\Infrastructure\DI
|
||||
*/
|
||||
class DIContainerTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Reset container before each test
|
||||
DIContainer::reset();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Reset container after each test
|
||||
DIContainer::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_return_singleton_instance(): void
|
||||
{
|
||||
$instance1 = DIContainer::getInstance();
|
||||
$instance2 = DIContainer::getInstance();
|
||||
|
||||
$this->assertSame($instance1, $instance2, 'getInstance() should return the same instance');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_prevent_cloning(): void
|
||||
{
|
||||
$this->expectError();
|
||||
$this->expectErrorMessage('Call to private');
|
||||
|
||||
$container = DIContainer::getInstance();
|
||||
$clone = clone $container; // This should trigger an error
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_prevent_unserialization(): void
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage('Cannot unserialize singleton');
|
||||
|
||||
$container = DIContainer::getInstance();
|
||||
$serialized = serialize($container);
|
||||
unserialize($serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_register_and_retrieve_service(): void
|
||||
{
|
||||
$container = DIContainer::getInstance();
|
||||
$service = new \stdClass();
|
||||
$service->name = 'Test Service';
|
||||
|
||||
$container->set('test_service', $service);
|
||||
|
||||
$this->assertTrue($container->has('test_service'), 'Container should have the registered service');
|
||||
$this->assertSame($service, $container->get('test_service'), 'Should retrieve the same service instance');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_return_null_for_non_existent_service(): void
|
||||
{
|
||||
$container = DIContainer::getInstance();
|
||||
|
||||
$this->assertFalse($container->has('non_existent'), 'Container should not have non-existent service');
|
||||
$this->assertNull($container->get('non_existent'), 'Should return null for non-existent service');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_throw_exception_for_unimplemented_component_repository(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('ComponentRepository not implemented yet');
|
||||
|
||||
$container = DIContainer::getInstance();
|
||||
$container->getComponentRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_throw_exception_for_unimplemented_validation_service(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('ValidationService not implemented yet');
|
||||
|
||||
$container = DIContainer::getInstance();
|
||||
$container->getValidationService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_throw_exception_for_unimplemented_cache_service(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('CacheService not implemented yet');
|
||||
|
||||
$container = DIContainer::getInstance();
|
||||
$container->getCacheService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_reset_singleton_instance(): void
|
||||
{
|
||||
$instance1 = DIContainer::getInstance();
|
||||
$instance1->set('test', new \stdClass());
|
||||
|
||||
DIContainer::reset();
|
||||
|
||||
$instance2 = DIContainer::getInstance();
|
||||
|
||||
$this->assertNotSame($instance1, $instance2, 'Reset should create a new instance');
|
||||
$this->assertFalse($instance2->has('test'), 'New instance should not have old services');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_should_manage_multiple_services(): void
|
||||
{
|
||||
$container = DIContainer::getInstance();
|
||||
|
||||
$service1 = new \stdClass();
|
||||
$service1->name = 'Service 1';
|
||||
|
||||
$service2 = new \stdClass();
|
||||
$service2->name = 'Service 2';
|
||||
|
||||
$service3 = new \stdClass();
|
||||
$service3->name = 'Service 3';
|
||||
|
||||
$container->set('service1', $service1);
|
||||
$container->set('service2', $service2);
|
||||
$container->set('service3', $service3);
|
||||
|
||||
$this->assertTrue($container->has('service1'));
|
||||
$this->assertTrue($container->has('service2'));
|
||||
$this->assertTrue($container->has('service3'));
|
||||
|
||||
$this->assertSame($service1, $container->get('service1'));
|
||||
$this->assertSame($service2, $container->get('service2'));
|
||||
$this->assertSame($service3, $container->get('service3'));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit Bootstrap
|
||||
*
|
||||
* Se ejecuta antes de todos los tests
|
||||
*/
|
||||
|
||||
// Cargar autoloader de Composer
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
// Definir constantes de WordPress si no existen (para tests unitarios puros)
|
||||
if (!defined('ABSPATH')) {
|
||||
define('ABSPATH', dirname(__DIR__, 5) . '/');
|
||||
}
|
||||
|
||||
if (!defined('WP_DEBUG')) {
|
||||
define('WP_DEBUG', true);
|
||||
}
|
||||
|
||||
// Para tests de integración, se cargará WordPress completo
|
||||
// Esto se hará en un bootstrap separado para tests de integración
|
||||
Reference in New Issue
Block a user