# Especificacion de Estandares de Codigo ## Purpose Define los principios SOLID, estandares de POO (Programacion Orientada a Objetos) y estandares generales de codigo que DEBEN seguirse en el desarrollo de ROITheme. ## Requirements ### Requirement: Principio de Responsabilidad Unica (SRP) Each class MUST have exactly one reason to change and one responsibility. #### Scenario: Responsabilidad de clase Use Case - **WHEN** se crea una clase Use Case - **THEN** DEBE manejar exactamente UNA operacion (Save, Get, Delete, etc.) - **AND** NO DEBE combinar multiples operaciones en una clase #### Scenario: Validacion de tamano de clase - **WHEN** se crea un archivo de clase - **THEN** DEBERIA tener menos de 300 lineas - **AND** DEBERIA tener maximo 3-5 metodos privados - **AND** el nombre de la clase DEBE describir su unica responsabilidad #### Scenario: Violacion de SRP - **WHEN** una clase contiene save(), get(), delete(), validate(), sendEmail() - **THEN** DEBE dividirse en clases Use Case separadas - **AND** cada clase maneja solo una operacion --- ### Requirement: Principio Abierto/Cerrado (OCP) Classes MUST be open for extension but closed for modification. #### Scenario: Agregar nuevo tipo de componente - **WHEN** se necesita un nuevo tipo de componente - **THEN** se DEBE crear una nueva subclase - **AND** la clase BaseComponent existente NO DEBE modificarse - **AND** NO se DEBERIAN agregar cadenas if/elseif para nuevos tipos #### Scenario: Extender funcionalidad base - **GIVEN** que existe una clase abstracta BaseComponent - **WHEN** se necesita comportamiento especializado - **THEN** se DEBE usar herencia para extender - **AND** la clase base DEBE permanecer sin cambios --- ### Requirement: Principio de Sustitucion de Liskov (LSP) Subclasses MUST be substitutable for their base classes without breaking functionality. #### Scenario: Uso polimorfico - **GIVEN** una funcion que acepta parametro BaseComponent - **WHEN** cualquier subclase es pasada - **THEN** la funcion DEBE funcionar correctamente - **AND** NO se DEBERIAN lanzar excepciones inesperadas #### Scenario: Cumplimiento de contrato - **WHEN** una subclase sobrescribe un metodo padre - **THEN** DEBE respetar el contrato del metodo original - **AND** las precondiciones NO DEBEN ser mas restrictivas - **AND** las postcondiciones NO DEBEN ser mas permisivas --- ### Requirement: Principio de Segregacion de Interfaces (ISP) Interfaces MUST be small and specific, not large and general. #### Scenario: Validacion de tamano de interface - **WHEN** se define una interface - **THEN** DEBE tener maximo 3-5 metodos - **AND** cada metodo DEBE relacionarse con la misma capacidad #### Scenario: Evitar interfaces gordas - **WHEN** existen multiples capacidades no relacionadas - **THEN** se DEBEN crear interfaces separadas - **AND** las clases implementan solo las interfaces que usan - **AND** NO se permiten metodos dummy "No implementado" #### Scenario: Diseno correcto de interface - **WHEN** se necesita funcionalidad de cache - **THEN** se DEBE usar CacheInterface con get(), set(), delete() - **AND** ValidatorInterface con validate() es separada --- ### Requirement: Principio de Inversion de Dependencias (DIP) High-level modules MUST depend on abstractions, not concrete implementations. #### Scenario: Inyeccion por constructor con interfaces - **WHEN** una clase necesita dependencias - **THEN** el constructor DEBE recibir interfaces, NO clases concretas - **AND** NO debe haber new ClaseConcreta() dentro del cuerpo de la clase #### Scenario: Cableado del Contenedor DI - **WHEN** se necesitan implementaciones concretas - **THEN** el DIContainer DEBE manejar el cableado - **AND** las clases permanecen desacopladas de las implementaciones #### Scenario: Dependencia incorrecta - **WHEN** el constructor hace this->repo = new WordPressNavbarRepository() - **THEN** esto DEBE refactorizarse para recibir NavbarRepositoryInterface - **AND** el DIContainer proporciona la implementacion concreta --- ### Requirement: Encapsulacion de Propiedades Class properties MUST be encapsulated with controlled access. #### Scenario: Visibilidad de propiedades - **WHEN** se define una propiedad de clase - **THEN** DEBE ser private o protected - **AND** el acceso DEBE ser via metodos getter - **AND** la mutacion DEBE ser via metodos setter o metodos de negocio #### Scenario: Encapsulacion de Value Object - **GIVEN** un ValueObject como ComponentName - **WHEN** es construido - **THEN** la validacion DEBE ocurrir en el constructor - **AND** el valor DEBE ser inmutable despues de la construccion - **AND** los detalles internos NO DEBEN exponerse --- ### Requirement: Guias de Herencia Inheritance MUST be used appropriately with limited depth. #### Scenario: Limite de profundidad de herencia - **WHEN** se usa herencia - **THEN** la profundidad maxima DEBE ser 2-3 niveles - **AND** las cadenas de herencia profundas DEBEN evitarse #### Scenario: Comportamiento comun en clase base - **WHEN** multiples clases comparten comportamiento comun - **THEN** se DEBERIA crear una clase base abstracta - **AND** las subclases especializan con comportamiento adicional --- ### Requirement: Polimorfismo Correcto Methods MUST accept base types or interfaces to enable polymorphism. #### Scenario: Tipos de parametros de metodo - **WHEN** un metodo acepta parametro de componente - **THEN** el type hint DEBERIA ser BaseComponent o ComponentInterface - **AND** cualquier subclase/implementacion DEBE funcionar correctamente #### Scenario: Polimorfismo de repository - **WHEN** un Use Case usa un repository - **THEN** DEBE aceptar RepositoryInterface - **AND** WordPressRepository y MockRepository funcionan transparentemente --- ### Requirement: Estandares PHP Estrictos All PHP code MUST follow strict type safety and naming conventions. #### Scenario: Declaracion de tipos estrictos - **WHEN** se crea un archivo PHP - **THEN** DEBE comenzar con declare(strict_types=1) - **AND** los tipos de retorno DEBEN declararse - **AND** los tipos de parametros DEBEN declararse #### Scenario: Convencion de namespace - **WHEN** se crea una clase - **THEN** el namespace DEBE seguir ROITheme\[Contexto]\[Componente]\[Capa] - **AND** DEBE soportar autoloading PSR-4 #### Scenario: Declaracion de clase - **WHEN** se crea una clase - **THEN** DEBERIA ser final por defecto - **AND** solo hacerla no-final cuando se pretende herencia - **AND** el nombre de clase DEBE ser PascalCase --- ### Requirement: Modularidad del Codigo Code MUST be organized into independent and cohesive modules. #### Scenario: Independencia de modulos - **WHEN** se crea un modulo - **THEN** DEBE ser autocontenido - **AND** NO DEBE depender de otros modulos (solo de Shared/) - **AND** eliminarlo NO DEBE romper otros modulos #### Scenario: Alta cohesion - **WHEN** el codigo se coloca en un modulo - **THEN** todo el codigo DEBE relacionarse con el proposito de ese modulo - **AND** el codigo no relacionado DEBE estar en Shared/ u otro modulo #### Scenario: Bajo acoplamiento - **WHEN** los modulos interactuan - **THEN** DEBEN comunicarse a traves de interfaces de Shared/ - **AND** las dependencias directas entre modulos estan prohibidas --- ### Requirement: DRY - No Te Repitas Code duplication MUST be eliminated through appropriate abstraction. #### Scenario: Ubicacion de codigo compartido - **WHEN** el codigo es usado por multiples modulos - **THEN** DEBE moverse al nivel apropiado de Shared/ - **AND** los modulos DEBEN importar de Shared/ #### Scenario: Deteccion de duplicacion - **WHEN** existe codigo similar en 2+ lugares - **THEN** DEBE refactorizarse a Shared/ - **AND** las ubicaciones originales importan de Shared/ --- ### Requirement: KISS - Mantenlo Simple Solutions MUST be simple and avoid over-engineering. #### Scenario: Uso de patrones - **WHEN** se considera un patron de diseno - **THEN** DEBE resolver un problema real - **AND** se DEBEN preferir soluciones mas simples - **AND** la abstraccion excesiva DEBE evitarse #### Scenario: Claridad del codigo - **WHEN** se escribe codigo - **THEN** DEBERIA ser auto-documentado - **AND** los comentarios DEBERIAN ser innecesarios para entender - **AND** la logica compleja DEBERIA extraerse a metodos bien nombrados --- ### Requirement: Separacion de Responsabilidades por Capa Each layer MUST have distinct responsibilities. #### Scenario: Responsabilidades por capa - **WHEN** se escribe codigo - **THEN** Domain contiene logica de negocio - **AND** Application contiene orquestacion - **AND** Infrastructure contiene implementacion tecnica - **AND** UI contiene solo presentacion #### Scenario: Validacion de responsabilidades cruzadas - **WHEN** se valida ubicacion de codigo - **THEN** SQL NO DEBE estar en Domain/Application - **AND** HTML NO DEBE estar en Domain/Application - **AND** logica de negocio NO DEBE estar en Infrastructure --- ### Requirement: Limites de Tamano de Archivo Files MUST be kept small and focused. #### Scenario: Tamano de archivo de clase - **WHEN** se crea un archivo de clase - **THEN** DEBERIA tener menos de 300 lineas - **AND** si es mas grande, DEBERIA dividirse en clases mas pequenas #### Scenario: Tamano de metodo - **WHEN** se escribe un metodo - **THEN** DEBERIA tener menos de 30 lineas - **AND** metodos complejos DEBERIAN extraerse a metodos auxiliares --- ### Requirement: Convenciones de Nomenclatura Names MUST be clear, descriptive, and follow conventions. #### Scenario: Nomenclatura de clases - **WHEN** se nombra una clase - **THEN** el nombre DEBE describir su unica responsabilidad - **AND** las clases Use Case DEBEN nombrarse [Accion][Entidad]UseCase - **AND** las clases Repository DEBEN nombrarse [Implementacion][Entidad]Repository #### Scenario: Nomenclatura de metodos - **WHEN** se nombra un metodo - **THEN** DEBE describir lo que hace el metodo - **AND** DEBERIA comenzar con un verbo - **AND** metodos booleanos DEBERIAN comenzar con is/has/can #### Scenario: Nomenclatura de variables - **WHEN** se nombra una variable - **THEN** DEBE ser descriptiva - **AND** las abreviaturas DEBEN evitarse - **AND** nombres de una letra solo para contadores de bucle --- ### Requirement: Validacion Pre-Commit Code MUST pass validation before commit. #### Scenario: Verificacion de cumplimiento SOLID - **WHEN** el codigo esta listo para commit - **THEN** SRP cada clase tiene una responsabilidad - **AND** OCP nuevas caracteristicas via extension, no modificacion - **AND** LSP las subclases son sustituibles - **AND** ISP las interfaces son pequenas 3-5 metodos - **AND** DIP el constructor recibe interfaces #### Scenario: Verificacion de cumplimiento POO - **WHEN** el codigo esta listo para commit - **THEN** las propiedades son private/protected - **AND** la profundidad de herencia es max 2-3 niveles - **AND** el polimorfismo esta implementado correctamente - **AND** la abstraccion oculta complejidad #### Scenario: Verificacion de calidad - **WHEN** el codigo esta listo para commit - **THEN** los archivos tienen menos de 300 lineas - **AND** los nombres son claros y descriptivos - **AND** no existe duplicacion de codigo - **AND** no hay sobre-ingenieria presente --- ### Requirement: Escaping Obligatorio en Output HTML All HTML output MUST use WordPress escaping functions for security. #### Scenario: Escaping de textos - **WHEN** se genera output de texto en HTML - **THEN** DEBE usar esc_html() para contenido de texto #### Scenario: Escaping de atributos - **WHEN** se genera un atributo HTML - **THEN** DEBE usar esc_attr() para valores de atributos #### Scenario: Escaping de URLs - **WHEN** se genera una URL en href o src - **THEN** DEBE usar esc_url() para URLs #### Scenario: Escaping de textareas - **WHEN** se genera contenido para textarea - **THEN** DEBE usar esc_textarea() para el valor #### Scenario: Prohibicion de output sin escaping - **WHEN** se revisa codigo de Renderer o FormBuilder - **THEN** NO DEBE existir echo o print de variables sin escaping - **AND** NO DEBE existir interpolacion directa de variables en HTML --- ## Ejemplos de Codigo PHP para SOLID > **REFERENCIA**: Para nomenclatura de clases, ver `_openspec/specs/nomenclatura.md` > **REFERENCIA**: Para ubicacion de archivos, ver `_openspec/specs/arquitectura-limpia.md` ### Ejemplo SRP: Single Responsibility Principle ```php renderContactForm($data); } elseif ($type === 'newsletter') { return $this->renderNewsletter($data); } elseif ($type === 'featured-image') { // Cada nuevo tipo = modificacion return $this->renderFeaturedImage($data); } return ''; } } // CORRECTO - Extender sin modificar (usando interfaces) // Domain/Contracts/RendererInterface.php use ROITheme\Shared\Domain\Entities\Component; interface RendererInterface { public function render(Component $component): string; public function supports(string $componentType): bool; } // ContactFormRenderer.php - implementa interface final class ContactFormRenderer implements RendererInterface { public function supports(string $componentType): bool { return $componentType === 'contact-form'; } public function render(Component $component): string { /* ... */ } } // NewsletterRenderer.php - nueva clase, sin modificar existentes final class NewsletterRenderer implements RendererInterface { public function supports(string $componentType): bool { return $componentType === 'newsletter'; } public function render(Component $component): string { /* ... */ } } ``` ### Ejemplo LSP: Liskov Substitution Principle ```php validate($data); // Siempre funciona if (!$result->isValid()) { // manejar error } } ``` ### Ejemplo ISP: Interface Segregation Principle ```php cssGenerator = new WordPressCSSGenerator(); } } // CORRECTO - Dependencia de abstraccion (interface) // Domain/Contracts/CSSGeneratorInterface.php interface CSSGeneratorInterface { public function generate(array $styles): string; } // Infrastructure/Ui/ContactFormRenderer.php final class ContactFormRenderer implements RendererInterface { public function __construct( private CSSGeneratorInterface $cssGenerator // Interface, no clase concreta ) {} public function render(Component $component): string { $data = $component->getData(); $css = $this->cssGenerator->generate($data['styles'] ?? []); // ... } } // Infrastructure/Services/WordPressCSSGenerator.php (implementacion) final class WordPressCSSGenerator implements CSSGeneratorInterface { public function generate(array $styles): string { /* ... */ } } // El DIContainer conecta la interface con la implementacion // functions.php o bootstrap $container->bind(CSSGeneratorInterface::class, WordPressCSSGenerator::class); ``` --- ## Manejo de Errores WordPress ### Cuando usar wp_die() ```php 403] ); } // Verificar permisos - error fatal si falla if (!current_user_can('manage_options')) { wp_die( esc_html__('Permission denied', 'roi-theme'), esc_html__('Error', 'roi-theme'), ['response' => 403] ); } } ``` ### Cuando usar WP_Error ```php 400] ); } // Operacion BD - puede fallar $result = $this->repository->save($data); if ($result === false) { return new \WP_Error( 'save_failed', __('Failed to save component settings', 'roi-theme'), ['status' => 500] ); } return $data; // Exito } // Uso del WP_Error $result = $service->saveComponentSettings($data); if (is_wp_error($result)) { // Manejar error $errorCode = $result->get_error_code(); $errorMessage = $result->get_error_message(); // ... } ``` ### Cuando usar Excepciones ```php value = $value; } } // Infrastructure captura y convierte a WP_Error si es necesario try { $componentName = new ComponentName($input); } catch (InvalidComponentNameException $e) { return new \WP_Error('invalid_component', $e->getMessage()); } ``` --- ## Sanitizacion y Validacion ### Tabla de Funciones de Sanitizacion | Tipo de Dato | Funcion | Ejemplo | |--------------|---------|---------| | Texto simple | `sanitize_text_field()` | Nombres, titulos | | Email | `sanitize_email()` | Direcciones de correo | | URL | `esc_url_raw()` | URLs para BD | | Entero positivo | `absint()` | IDs, cantidades | | Entero (puede ser negativo) | `intval()` | Posiciones, offsets | | HTML seguro | `wp_kses_post()` | Contenido con formato | | Nombre de archivo | `sanitize_file_name()` | Uploads | | Key/slug | `sanitize_key()` | component_name | | Clase CSS | `sanitize_html_class()` | Clases dinamicas | | Textarea | `sanitize_textarea_field()` | Textos multilinea | | Hexadecimal (color) | `sanitize_hex_color()` | Colores | ### Ejemplos de Sanitizacion ```php sanitize_text_field($_POST['name'] ?? ''), 'email' => sanitize_email($_POST['email'] ?? ''), 'phone' => sanitize_text_field($_POST['phone'] ?? ''), 'message' => sanitize_textarea_field($_POST['message'] ?? ''), 'url' => esc_url_raw($_POST['url'] ?? ''), 'post_id' => absint($_POST['post_id'] ?? 0), ]; // Validar despues de sanitizar if (empty($data['name']) || empty($data['email'])) { wp_send_json_error(['message' => 'Name and email are required']); return; } if (!is_email($data['email'])) { wp_send_json_error(['message' => 'Invalid email address']); return; } // Procesar datos sanitizados y validados $this->processForm($data); wp_send_json_success(['message' => 'Form submitted successfully']); } ``` ### Sanitizacion de Arrays y JSON ```php sanitize_key($data['component_name'] ?? ''), 'visibility' => [ 'is_enabled' => (bool)($data['visibility']['is_enabled'] ?? false), 'show_on_desktop' => (bool)($data['visibility']['show_on_desktop'] ?? true), 'show_on_mobile' => (bool)($data['visibility']['show_on_mobile'] ?? true), ], 'content' => [ 'title' => sanitize_text_field($data['content']['title'] ?? ''), 'description' => wp_kses_post($data['content']['description'] ?? ''), ], 'styles' => [ 'background_color' => sanitize_hex_color($data['styles']['background_color'] ?? ''), 'text_color' => sanitize_hex_color($data['styles']['text_color'] ?? ''), 'padding' => absint($data['styles']['padding'] ?? 0), ], ]; } // Sanitizar JSON recibido public function sanitizeJsonInput(string $json): array { $data = json_decode($json, true); if (!is_array($data)) { return []; } return $this->sanitizeComponentData($data); } ``` --- ## Escaping para Output ### Tabla de Funciones de Escaping | Contexto | Funcion | Cuando usar | |----------|---------|-------------| | Texto en HTML | `esc_html()` | Contenido entre tags | | Atributo HTML | `esc_attr()` | Valores de atributos | | URL en href/src | `esc_url()` | Links, imagenes | | Textarea value | `esc_textarea()` | Contenido de textarea | | HTML permitido | `wp_kses_post()` | Contenido con formato | | JavaScript | `esc_js()` | Strings en JS inline | | Traduccion + escape | `esc_html__()` | Textos traducibles | | Traduccion + attr | `esc_attr__()` | Atributos traducibles | ### Ejemplos de Escaping en Renderers ```php getData(); $title = $data['content']['title'] ?? ''; $description = $data['content']['description'] ?? ''; $buttonText = $data['content']['button_text'] ?? 'Submit'; $buttonUrl = $data['content']['button_url'] ?? '#'; $customClass = $data['styles']['custom_class'] ?? ''; $html = '
'; // esc_html() para texto visible $html .= '

' . esc_html($title) . '

'; // wp_kses_post() para HTML permitido $html .= '
' . wp_kses_post($description) . '
'; // esc_url() para URLs $html .= ''; // esc_html() para texto del boton $html .= esc_html($buttonText); $html .= ''; $html .= '
'; return $html; } } ``` ### Escaping con Traducciones ```php ' . esc_html__('Email Address', 'roi-theme') . ''; $html .= ''; // CORRECTO - Con sprintf para variables $html .= sprintf( /* translators: %s: user name */ esc_html__('Hello, %s!', 'roi-theme'), esc_html($userName) ); // INCORRECTO - NO escapar resultado de traduccion con variable $html .= esc_html(sprintf(__('Hello, %s!', 'roi-theme'), $userName)); // XSS vulnerable ``` --- ## Hooks WordPress ### add_action() - Cuando y Como ```php renderComponents(); return $content . $components; // Retornar modificado } // Filtro con multiples argumentos public function modifyThumbnail( string $html, int $postId, int $thumbId, string $size, array $attr ): string { // Todos los parametros disponibles return str_replace('class="', 'class="lazy-load ', $html); } } ``` ### Nomenclatura de Hooks Propios ```php generateComponentData($componentName); set_transient('roi_theme_component_cache_' . $componentName, $cached, HOUR_IN_SECONDS); } return $cached; // Eliminar transient (cuando datos cambian) delete_transient('roi_theme_component_cache_' . $componentName); // Eliminar todos los transients del tema (cleanup) public function clearAllComponentCache(): void { global $wpdb; $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_roi_theme_component_cache_%' OR option_name LIKE '_transient_timeout_roi_theme_component_cache_%'" ); } ``` ### Object Cache ```php loadComponentFromDB($componentName); wp_cache_set('component_' . $componentName, $cached, 'roi_theme', 3600); } // Eliminar de cache wp_cache_delete('component_' . $componentName, 'roi_theme'); // Eliminar grupo completo wp_cache_flush_group('roi_theme'); // Solo si object cache soporta ``` ### Cleanup en Desactivacion/Desinstalacion ```php query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_roi_theme_%'" ); // Limpiar scheduled events wp_clear_scheduled_hook('roi_theme_daily_cleanup'); } function roi_theme_uninstall(): void { // Solo en desinstalacion completa // Eliminar opciones delete_option('roi_theme_settings'); delete_option('roi_theme_version'); // Eliminar tabla personalizada (si existe) global $wpdb; $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}roi_theme_component_settings"); // Eliminar user meta delete_metadata('user', 0, 'roi_theme_preferences', '', true); } ``` --- ## Checklist Pre-Commit Detallado ### Verificaciones de Sintaxis - [ ] `declare(strict_types=1)` al inicio de cada archivo PHP - [ ] Namespace correcto: `ROITheme\[Context]\[Component]\[Layer]` - [ ] Tipos de retorno declarados en todos los metodos - [ ] Tipos de parametros declarados - [ ] Sin errores de PHP (ejecutar archivo) ### Verificaciones de Estilo - [ ] Clases son `final` por defecto - [ ] Propiedades son `private` o `protected` - [ ] Nombres de clase en PascalCase - [ ] Nombres de metodos en camelCase - [ ] Nombres de constantes en UPPER_SNAKE_CASE - [ ] Archivos <= 300 lineas - [ ] Metodos <= 30 lineas ### Verificaciones de Seguridad - [ ] Variables de usuario sanitizadas (`sanitize_*()`) - [ ] Output escapado (`esc_*()`) - [ ] Nonce verificado en forms/AJAX - [ ] Permisos verificados (`current_user_can()`) - [ ] Sin SQL directo (usar `$wpdb->prepare()`) - [ ] Sin `eval()`, `exec()`, `shell_exec()` - [ ] Sin `$_REQUEST` (usar `$_POST` o `$_GET` especifico) ### Verificaciones de Arquitectura - [ ] Domain NO tiene dependencias de WordPress - [ ] Domain NO tiene echo/print/HTML - [ ] Application NO tiene dependencias de Infrastructure - [ ] Infrastructure implementa interfaces de Domain - [ ] DI via constructor (interfaces, no clases concretas) - [ ] Sin `new ClaseConcreta()` fuera del DIContainer ### Verificaciones de Nomenclatura - [ ] component_name en kebab-case - [ ] Carpetas de modulo en PascalCase - [ ] Archivos JSON schema en kebab-case - [ ] `supports()` retorna kebab-case - [ ] Hooks propios con prefijo `roi_theme_` ### Verificaciones de WordPress - [ ] Hooks registrados con prioridad explicita - [ ] Texto traducible usa `__()` o `_e()` - [ ] Assets encolados correctamente (no hardcoded) - [ ] AJAX handlers usan `wp_send_json_*` - [ ] Transients usan constantes de tiempo --- **Última actualización:** 2026-01-08