{"id":60,"date":"2025-04-11T11:49:01","date_gmt":"2025-04-11T15:49:01","guid":{"rendered":"https:\/\/juredev.com\/blog\/?p=60"},"modified":"2025-04-12T20:10:00","modified_gmt":"2025-04-13T00:10:00","slug":"composite-db-orm-ultraligero-para-php-moderno-implementa-velocidad-en-tus-soluciones","status":"publish","type":"post","link":"https:\/\/juredev.com\/blog\/2025\/04\/composite-db-orm-ultraligero-para-php-moderno-implementa-velocidad-en-tus-soluciones\/","title":{"rendered":"Composite DB: ORM Ultraligero para PHP Moderno &#8211; Implementa Velocidad en tus Soluciones"},"content":{"rendered":"\n<p>Si hay algo que me apasiona en el desarrollo web, es encontrar herramientas que simplifiquen la vida sin comprometer el rendimiento. ORMs como Doctrine son potentes, pero a menudo vienen con configuraciones complejas y una curva de aprendizaje empinada. Hace poco me encontr\u00e9 <strong>Composite DB<\/strong>, un ORM minimalista que puedes tener funcionando en menos de 5 minutos. \u00bfTe gustar\u00eda conocer una opci\u00f3n ligera y r\u00e1pida para tus proyectos en PHP 8.1+? \u00a1Sigue leyendo!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfQu\u00e9 es Composite DB?<\/h2>\n\n\n\n<p><strong>Composite<\/strong> <strong>DB<\/strong> es un ORM ultraligero dise\u00f1ado para PHP 8.1+. Su filosof\u00eda es clara: menos configuraci\u00f3n, m\u00e1s acci\u00f3n. Aprovecha las caracter\u00edsticas modernas de PHP para ofrecerte un c\u00f3digo limpio y un rendimiento \u00e1gil, ideal para proyectos donde la velocidad y la simplicidad son prioridad.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lo mejor de PHP 8.1+ en tus manos<\/h2>\n\n\n\n<p><strong>Composite DB<\/strong> saca el m\u00e1ximo provecho de las novedades de PHP. Mira este ejemplo de una entidad:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n&lt;?php\n\/\/ src\/Entity\/User.php\nnamespace App\\Entity;\n\nuse Composite\\DB\\Attributes\\{Table, PrimaryKey};\nuse Composite\\Entity\\AbstractEntity;\nuse App\\Enums\\Status;\n\n#[Table(connection: 'sqlite', name: 'Users')]\nclass User extends AbstractEntity\n{\n    #[PrimaryKey(autoIncrement: true)]\n    public readonly int $id;\n\n    public function __construct(\n        public string $email,\n        public ?string $name = null,\n        public bool $is_test = false,\n        public Status $status = Status::ACTIVE,\n        public readonly \\DateTimeImmutable $created_at = new \\DateTimeImmutable(),\n    ) {}\n}\n<\/code><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Enums nativos<\/strong>: Usa Status para definir estados como <code>ACTIVE<\/code> o <code>BLOCKED<\/code> de forma clara.<\/li>\n\n\n\n<li><strong>Propiedades inmutables<\/strong>: <code>id<\/code> y <code>created_at<\/code> son <code>readonly<\/code>, garantizando datos consistentes.<\/li>\n\n\n\n<li><strong>Atributos PHP<\/strong>: Con <code>#[Table]<\/code> y <code>#[PrimaryKey]<\/code>, mapeas la entidad a la base de datos sin complicaciones.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfCu\u00e1ndo brilla Composite DB?<\/h2>\n\n\n\n<p>Este ORM es perfecto para escenarios donde necesitas moverte r\u00e1pido:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>APIs RESTful<\/strong>: Crea endpoints para apps m\u00f3viles en minutos.<\/li>\n\n\n\n<li><strong>Prototipos r\u00e1pidos<\/strong>: Valida ideas sin perder tiempo en configuraciones.<\/li>\n\n\n\n<li><strong>Proyectos IoT<\/strong>: Gestiona datos de sensores con pocos recursos.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Tutorial pr\u00e1ctico: API con Composite DB y Slim Framework<\/h2>\n\n\n\n<p>Vamos a construir una API sencilla usando <strong>Composite DB<\/strong> y <strong>Slim <\/strong>con <strong>SQLite <\/strong>como base de datos. \u00a1Sigue estos pasos y tendr\u00e1s algo funcional en poco tiempo!<\/p>\n\n\n\n<p><strong>1.- Instalar dependencias<\/strong><\/p>\n\n\n\n<p>Crea un nuevo proyecto e instala las dependencias con Composer:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\nmkdir CompositeDB-Slim\ncd CompositeDB-Slim\ncomposer init --name=\"tu-nombre\/composite-db-slim\" --require=\"php:^8.1\"\ncomposer require slim\/slim:^4.0 slim\/psr7 composite\/db vlucas\/phpdotenv\n<\/code><\/p>\n\n\n\n<p><strong>2.- Configurar la conexi\u00f3n a la base de datos<\/strong><\/p>\n\n\n\n<p><strong>Composite DB<\/strong> necesita un archivo de configuraci\u00f3n para la conexi\u00f3n. Crea <code>src\/Config\/ConnectionManager.php<\/code>:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n<?php\ndeclare(strict_types=1);\n\n\/\/ Definir la ruta base para el archivo de la base de datos\nconst DB_PATH = __DIR__ . '\/..\/..\/database\/database.db';\n\nreturn [\n    'sqlite' => [\n        'driver' => 'pdo_sqlite',\n        'path' => DB_PATH, \/\/SQLite crear\u00e1 este archivo si no existe\n    ],\n];\n<\/code><\/p>\n\n\n\n<p>Luego, configura la variable de entorno <code>CONNECTIONS_CONFIG_FILE<\/code> usando <code>phpdotenv<\/code>. Crea un archivo <code>.env<\/code> en la ra\u00edz del proyecto:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\nCONNECTIONS_CONFIG_FILE=..\/src\/Config\/ConnectionManager.php\n<\/code><\/p>\n\n\n\n<p><strong>Tip<\/strong>: La ruta <code>..\/src\/Config\/ConnectionManager.php<\/code> es relativa al directorio <code>public\/<\/code>, desde donde se ejecuta <code>index.php<\/code>. Esto asegura que Composite DB encuentre el archivo correctamente.<\/p>\n\n\n\n<p><strong>3.- Definir el modelo y la tabla<\/strong><\/p>\n\n\n\n<p>Crea el enum <code>Status<\/code> en <code>src\/Enums\/Status.php<\/code>:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n&lt;?php\nnamespace App\\Enums;\n\nenum Status: string\n{\n    case ACTIVE = 'ACTIVE';\n    case BLOCKED = 'BLOCKED';\n}\n<\/code><\/p>\n\n\n\n<p>Usa la entidad User que mostramos antes <code>(src\/Model\/User.php)<\/code>.<\/p>\n\n\n\n<p>Ahora, define la clase de tabla en <code>src\/Model\/UsersTable.php<\/code>:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n&lt;?php\nnamespace App\\Table;\n\nuse Composite\\DB\\AbstractTable;\nuse App\\Entity\\User;\n\nclass UsersTable extends \\Composite\\DB\\AbstractTable\n{\n    protected function getConfig(): \\Composite\\DB\\TableConfig\n    {\n        return \\Composite\\DB\\TableConfig::fromEntitySchema(User::schema());\n    }\n\n    \/**\n     * @return User[]\n     *\/\n    public function findAllActive(): array\n    {\n        return $this->_findAll(['status' => \\App\\Enums\\Status::ACTIVE]);\n    }\n\n    public function init(): void\n    {\n        $this->getConnection()->executeStatement(\"\n            CREATE TABLE IF NOT EXISTS Users\n            (\n                `id`         INTEGER\n                    CONSTRAINT Users_pk PRIMARY KEY AUTOINCREMENT,\n                `email`      VARCHAR(255)                           NOT NULL,\n                `name`       VARCHAR(255) DEFAULT NULL,\n                `is_test`    INT          DEFAULT 0                 NOT NULL,\n                `status`     TEXT         DEFAULT 'ACTIVE'          NOT NULL,\n                `created_at` TIMESTAMP    DEFAULT CURRENT_TIMESTAMP NOT NULL,\n                CHECK (status IN ('ACTIVE', 'BLOCKED'))\n            );\n        \");\n    }\n}\n<\/code><\/p>\n\n\n\n<p><strong>Nota:<\/strong> Llama a <code>init()<\/code> una vez para crear la tabla. Puedes hacerlo manualmente o en el c\u00f3digo inicial (lo veremos m\u00e1s adelante).<\/p>\n\n\n\n<p><strong>4.- Configurar Slim y las rutas<\/strong><\/p>\n\n\n\n<p>Crea <code>public\/index.php<\/code> con la l\u00f3gica de la API:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n&lt;?php\ndeclare(strict_types=1);\n\nuse App\\Controllers\\RootController;\nuse App\\Controllers\\UserController;\nuse DI\\ContainerBuilder;\nuse Psr\\Http\\Message\\ResponseInterface as Response;\nuse Psr\\Http\\Message\\ServerRequestInterface as Request;\nuse Slim\\Factory\\AppFactory;\nuse Dotenv\\Dotenv;\n\nrequire __DIR__ . '\/..\/vendor\/autoload.php';\n\n\/\/ Cargar variables de entorno\n$dotenv = Dotenv::createImmutable(__DIR__ . '\/..\/');\n$dotenv->load();\n\n\/\/ Configurar PHP-DI\n$containerBuilder = new ContainerBuilder();\n$containerBuilder->addDefinitions([\n    \\App\\Table\\UsersTable::class => \\DI\\create(\\App\\Table\\UsersTable::class)\n]);\n\n\/\/ Construir el contenedor\n$container = $containerBuilder->build();\n\n\/\/ Configurar Slim con el contenedor\nAppFactory::setContainer($container);\n$app = AppFactory::create();\n$app->addRoutingMiddleware();\n\n\/\/ Inicializar la base de datos (descomentar solo la primera vez)\n\/\/ $container->get(App\\Table\\UsersTable::class)->init();\n\n\/\/ Rutas\n$app->get('\/', [RootController::class, 'index']);\n$app->get('\/users', [UserController::class, 'list']);\n$app->post('\/users', [UserController::class, 'create']);\n\n$app->run();\n<\/code><\/p>\n\n\n\n<p><strong>5.- Probar la API<\/strong><\/p>\n\n\n\n<p>Crea el directorio <code>database\/<\/code> en la ra\u00edz del proyecto:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\nmkdir database\n<\/code><\/p>\n\n\n\n<p><strong>Nota: <\/strong>En el repositorio incluyo una base datos SQlite con ejemplos pero si deseas hacerlo desde cero, este es el inicio.<\/p>\n\n\n\n<p>Inicia el servidor de desarrollo:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\nphp -S localhost:8000 -t public\n<\/code><\/p>\n\n\n\n<p>Prueba las rutas:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Listar usuarios activos<\/strong>:<\/li>\n<\/ul>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\ncurl http:\/\/localhost:8000\/\n<\/code><\/p>\n\n\n\n<p>Respuesta esperada:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n{\n      \"data\": [],\n      \"message\": \"Usuarios activos obtenidos correctamente\"\n}\n<\/code><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Mostrar los usuarios activos<\/strong>:<\/li>\n<\/ul>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\ncurl -X GET http:\/\/localhost:8000\/users\n<\/code><\/p>\n\n\n\n<p>Respuesta esperada:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n{\n    \"status\": \"success\",\n    \"data\": [\n        {\n            \"id\": 1,\n            \"email\": \"user@example.com\",\n            \"name\": \"John\",\n            \"is_test\": false,\n            \"status\": \"ACTIVE\",\n            \"created_at\": \"2025-04-10 22:24:20.128227\"\n        },\n        {\n            \"id\": 2,\n            \"email\": \"user@example.com\",\n            \"name\": \"Martie\",\n            \"is_test\": false,\n            \"status\": \"ACTIVE\",\n            \"created_at\": \"2025-04-10 22:56:49.105486\"\n        }\n    ],\n    \"message\": \"Usuarios activos obtenidos correctamente\"\n}\n<\/code><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Crear un usuario<\/strong>:<\/li>\n<\/ul>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\ncurl -X POST http:\/\/localhost:8000\/users -H \"Content-Type: application\/json\" -d '{\"email\":\"test@example.com\",\"name\":\"Test User\"}'\n<\/code><\/p>\n\n\n\n<p>Respuesta esperada:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n{\n      \"data\": {\n          \"id\": 1,\n          \"email\": \"test@example.com\",\n          \"name\": \"Test User\",\n          \"is_test\": false,\n          \"status\": \"ACTIVE\",\n          \"created_at\": \"2025-04-10T20:00:00+00:00\"\n      },\n      \"message\": \"Usuario creado correctamente\"\n}\n<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Limitaciones y c\u00f3mo sacarle partido<\/h2>\n\n\n\n<p><strong>Composite DB<\/strong> no est\u00e1 dise\u00f1ado para relaciones complejas (como joins autom\u00e1ticos), pero eso no es un obst\u00e1culo si sabes adaptarte:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Consultas SQL manuales<\/strong>: Usa <code>$usersTable-&gt;getConnection()-&gt;executeQuery()<\/code> para consultas personalizadas.<\/li>\n\n\n\n<li><strong>Cach\u00e9 opcional<\/strong>: Aunque no lo usamos aqu\u00ed, Composite DB soporta cach\u00e9 ligero para acelerar consultas recurrentes.<\/li>\n\n\n\n<li><strong>Simplicidad como fortaleza<\/strong>: Su enfoque minimalista reduce el overhead y mantiene el c\u00f3digo claro.<\/li>\n<\/ul>\n\n\n\n<p>Por ejemplo, para un join manual:<\/p>\n\n\n\n<p><code style=\"background-color:black;color:white;padding:10px 5px;border-radius:3px;white-space:pre;font-family:monospace;display:block;overflow-x:auto;\">\n\/**\n * @return User[]\n *\/\npublic function findAllActiveAdults(): array\n{\n    return $this->_findAll(\n        new Where(\n            'age > :age AND status = :status',\n            ['age' => 18, 'status' => Status::ACTIVE->name],\n        )\n    );\n}\n<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lecciones aprendidas de la documentaci\u00f3n<\/h2>\n\n\n\n<p>La documentaci\u00f3n de Composite DB puede ser confusa:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Rutas relativas<\/strong>: No siempre queda claro c\u00f3mo configurar <code>CONNECTIONS_CONFIG_FILE<\/code>. Usar <code>phpdotenv<\/code> y una ruta relativa desde <code>public\/<\/code> (como <code>..\/src\/Config\/ConnectionManager.php<\/code>) resuelve el problema.<\/li>\n\n\n\n<li><strong>Configuraci\u00f3n inicial<\/strong>: Tuve que experimentar para entender c\u00f3mo el ORM carga el archivo de conexi\u00f3n. Mi consejo: verifica que las rutas sean correctas seg\u00fan tu estructura de carpetas.<\/li>\n<\/ul>\n\n\n\n<p>Recomendamos usar <code>phpdotenv<\/code> para una configuraci\u00f3n m\u00e1s limpia y asegurarte de que la ruta al archivo de configuraci\u00f3n sea correcta seg\u00fan tu estructura de proyecto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfQui\u00e9n est\u00e1 usando Composite DB?<\/h2>\n\n\n\n<p>Este ORM est\u00e1 ganando adeptos en:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Startups<\/strong>: Ideal para lanzar productos r\u00e1pidamente.<\/li>\n\n\n\n<li><strong>DevOps<\/strong>: F\u00e1cil de integrar con herramientas como Prometheus o Grafana.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfEs Composite DB para ti?<\/h2>\n\n\n\n<p>Si valoras:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Simplicidad extrema<\/strong>: Configuraci\u00f3n m\u00ednima, resultados inmediatos.<\/li>\n\n\n\n<li><strong>Rendimiento \u00e1gil<\/strong>: Como si fuera serverless.<\/li>\n\n\n\n<li><strong>PHP moderno<\/strong>: Aprovecha enums, propiedades readonly y atributos.<\/li>\n<\/ul>\n\n\n\n<p>\u2026\u00a1Entonces es tu herramienta! Si necesitas migraciones complejas o transacciones avanzadas, considera opciones como Doctrine.<\/p>\n\n\n\n<p><strong>Composite DB<\/strong> es una joya para proyectos peque\u00f1os, experimentos o cualquier situaci\u00f3n donde quieras velocidad y simplicidad. Te dejo el c\u00f3digo de ejemplo en este <a href=\"https:\/\/github.com\/jure-ve\/CompositeDB-Slim\">repositorio<\/a> para que lo explores. \u00bfTe animas a probarlo en tu pr\u00f3ximo proyecto?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Si hay algo que me apasiona en el desarrollo web, es encontrar herramientas que simplifiquen la vida sin comprometer el rendimiento. ORMs como Doctrine son potentes, pero a menudo vienen con configuraciones complejas y una curva de aprendizaje empinada. Hace poco me encontr\u00e9 Composite DB, un ORM minimalista que puedes tener funcionando en menos de [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[15],"class_list":["post-60","post","type-post","status-publish","format-standard","hentry","category-desarrollo","tag-php"],"_links":{"self":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/60","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/comments?post=60"}],"version-history":[{"count":0,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/60\/revisions"}],"wp:attachment":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/media?parent=60"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/categories?post=60"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/tags?post=60"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}