12. Herramientas de desarrollo
12.1. Comandos Artesano
El framework implementa un sistema de comandos CLI denominado "Artesano", similar a Artisan en Laravel, que facilita la generación de código y automatiza tareas repetitivas para los desarrolladores. Este sistema está construido sobre el componente Console de Symfony.
Arquitectura
El ejecutable principal artesano ubicado en la raíz del proyecto inicializa una aplicación de consola Symfony:
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Console\Application;
use App\Commands\CreateDomainCommand;
use App\Commands\CreateLegacyInfraestructureCommand;
$app = new Application();
try {
$createDomainCommand = new CreateDomainCommand();
$createDomainCommand->setAliases(['cd', 'create-domain']);
$createLegacyCommand = new CreateLegacyInfraestructureCommand();
$createLegacyCommand->setAliases(['cli', 'create-legacy']);
$app->add($createDomainCommand);
$app->add($createLegacyCommand);
} catch (\Exception $e) {
echo "Error al registrar comandos: " . $e->getMessage() . "\n";
echo $e->getTraceAsString() . "\n";
}
$app->run();
Los comandos se implementan como clases PHP que extienden de Symfony\Component\Console\Command\Command y se registran en la aplicación principal, permitiendo ejecutarlos mediante el CLI.
12.2. Generación de código
El framework incluye dos comandos principales para la generación de código que aceleran el desarrollo:
CreateDomainCommand
Este comando genera automáticamente la estructura completa de un nuevo dominio de negocio, creando todos los archivos necesarios según la arquitectura hexagonal implementada en el framework:
class CreateDomainCommand extends Command
{
protected function configure()
{
$this->setName('app:create-domain')
->setDescription('Crea un nuevo Domain con su estructura basica')
->addArgument('domain', InputArgument::REQUIRED, 'Nombre del domain')
->addArgument('infrastructure', InputArgument::REQUIRED, 'Nombre de la infraestructura principal')
->addArgument('module', InputArgument::OPTIONAL, 'Nombre del modulo');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// Lógica para generar la estructura del dominio
// ...
}
}
El comando crea:
- Controlador del dominio
- Servicio del dominio
- Rutas para los endpoints REST
- DTOs para solicitudes y respuestas
- Estructura de directorios adecuada
Ejemplo de uso:
php artesano app:create-domain Products p-maes_maesarti Catalog
Este comando generaría un nuevo dominio "Products" que utiliza la infraestructura "maesarti" del módulo POS ("p-maes") dentro del módulo "Catalog".
CreateLegacyInfraestructureCommand
Este comando genera la estructura de infraestructura para integrar con sistemas legacy, particularmente con el sistema Quartup:
class CreateLegacyInfraestructureCommand extends Command
{
protected static $defaultName = 'app:create-legacy-infrastructure';
protected $basePath = 'src/Infrastructures/Legacy/';
protected function configure()
{
$this
->setName('app:create-legacy-infrastructure')
->setDescription('Create a new legacy infrastructure')
->addArgument('module', InputArgument::REQUIRED, 'Nombre del modulo P Quartup')
->addArgument('resource', InputArgument::REQUIRED, 'Nombre amigable de la tabla')
->addArgument('table', InputArgument::REQUIRED, 'Nombre de la tabla legacy')
->setHelp('Este comando crea una nueva infraestructura legacy');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// Lógica para generar la infraestructura legacy
// ...
}
}
Crea:
- Modelo para la tabla legacy
- Repositorio correspondiente
- Configuración de mapeo entre el sistema legacy y la API
Ejemplo de uso:
php artesano app:create-legacy-infrastructure PPos PosartticVArticulo posarttic
Este comando crearía la infraestructura para la tabla "posarttic" del módulo "PPos" de Quartup, con un nombre amigable de "PosartticVArticulo".
12.3. Generación de plantillas
Los comandos de generación utilizan plantillas incorporadas para crear archivos con una estructura consistente. Por ejemplo, para la creación de un servicio de dominio:
private function getServiceTemplate($module, $domain, $infrastructure, $infrastructureNamespace, $infrastructureCamelFirstLower)
{
$namespace = $module
? "ApiLayer\\Modules\\{$module}\\Domains\\{$domain}"
: "ApiLayer\\Domains\\{$domain}";
return "<?php
namespace {$namespace};
use App\Base\DomainService;
use App\Core\RequestFactory as Request;
use {$infrastructureNamespace}\\{$infrastructure}Repository;
use {$namespace}\DTOs\\Res{$infrastructure}DTO;
use {$namespace}\DTOs\Req{$infrastructure}DTO;
class {$domain}Service extends DomainService
{
protected \${$infrastructureCamelFirstLower}Repository;
protected \$res{$infrastructure}DTO;
protected \$req{$infrastructure}DTO;
public function __construct(
{$infrastructure}Repository \${$infrastructureCamelFirstLower}Repository,
Res{$infrastructure}DTO \$res{$infrastructure}DTO,
Req{$infrastructure}DTO \$req{$infrastructure}DTO
) {
\$this->{$infrastructureCamelFirstLower}Repository = \${$infrastructureCamelFirstLower}Repository;
\$this->res{$infrastructure}DTO = \$res{$infrastructure}DTO;
\$this->req{$infrastructure}DTO = \$req{$infrastructure}DTO;
\$this->{$infrastructureCamelFirstLower}Repository->selectable(Res{$infrastructure}DTO::\$selectables);
}
// Métodos CRUD y de servicio
// ...
}";
}
Estos métodos de generación de plantillas son extensivos y cubren todos los componentes necesarios para la implementación de dominios completos.
12.4. Debugging y pruebas
Herramienta de debugging
El framework incluye una función de debugging global ddd() (dump, die, debug) para facilitar la inspección de variables durante el desarrollo:
if (!function_exists('ddd')) {
function ddd(...$a)
{
QuApp::markAsNormalTermination();
dd(...$a);
}
}
Esta función marca la terminación como normal en la aplicación antes de volcar los datos, lo que permite evitar la activación de los manejadores de error cuando se utiliza deliberadamente.
Configuración de entorno
El framework detecta automáticamente el entorno de ejecución (desarrollo o producción):
static public function setEnvironment($env = null)
{
self::$environment = 'PROD';
if (isset(self::$QuupIni['id_domain'])) {
if (self::$QuupIni['id_domain'] == 3) {
self::$environment = 'DEV';
}
}
if ($env) {
if ('PROD' === strtoupper($env)) {
self::$environment = 'PROD';
return;
}
if ('DEV' === strtoupper($env)) {
self::$environment = 'DEV';
return;
}
}
return;
}
El entorno determina el comportamiento del sistema en cuanto a:
- Nivel de detalle en los mensajes de error
- Mostrado de datos sensibles
- Cantidad de información de debugging expuesta
Trazas de errores
Para facilitar la depuración, el framework proporciona un sistema de trazas de errores:
try {
/**
* procesamos la peticiónn y obtenemos la respuesta
*/
$response = $kernel->handle($request);
QuApp::setSuccessResponse($response);
} catch (\Throwable $th) {
if (ENVIRONMENT === "DEV") {
QuApp::setTraceErrors(true);
}
QuApp::setErrorResponse($th);
}
En el entorno de desarrollo, las trazas completas de error son capturadas y formateadas para ayudar a identificar la fuente del problema.
Conversión de errores a excepciones
El framework implementa un mecanismo para convertir los errores PHP en excepciones, lo que facilita el manejo uniforme de errores:
class QuErrorsToException
{
protected $errorLevelsToConvert;
public function __construct($errorLevelsToConvert = E_ALL)
{
$this->errorLevelsToConvert = $errorLevelsToConvert;
}
public function register()
{
set_error_handler([$this, 'handleError']);
}
public function handleError($severity, $message, $file, $line)
{
if (!($severity & $this->errorLevelsToConvert)) {
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
}
Esta clase permite configurar qué tipos de errores deben convertirse en excepciones, y se inicializa de manera diferente según el entorno:
if (ENVIRONMENT == 'DEV') {
$converter = new QuErrorsToException(E_ALL); // Todo menos notice
} else {
$converter = new QuErrorsToException(E_ALL & ~(E_NOTICE | E_WARNING)); // todo menos warning y notice
}
$converter->register();
Estructura de herramientas de desarrollo
Con estas herramientas, el framework ofrece un entorno de desarrollo más productivo donde los desarrolladores pueden:
- Generar rápidamente nuevos componentes con estructura consistente
- Integrar fácilmente con sistemas legacy
- Depurar eficientemente las aplicaciones
- Manejar errores de forma consistente y rastreable
Esta serie de herramientas reduce significativamente el tiempo de desarrollo y mantiene la coherencia en la base de código, asegurando que todos los componentes sigan los patrones de diseño establecidos.