{"id":139,"date":"2017-05-04T18:05:08","date_gmt":"2017-05-04T23:05:08","guid":{"rendered":"https:\/\/drmad.org\/blog\/?p=139"},"modified":"2021-06-20T22:52:29","modified_gmt":"2021-06-21T03:52:29","slug":"nuevo-orm-para-vendimia","status":"publish","type":"post","link":"https:\/\/drmad.org\/blog\/nuevo-orm-para-vendimia.html","title":{"rendered":"Nuevo ORM para Vendimia"},"content":{"rendered":"\n<p>No he tenido tiempo de escribir sobre mi <a href=\"https:\/\/drmad.org\/blog\/m3-framework.html\">framework de desarrollo web Vendimia<\/a>. Y ahora he tenido una idea para mejorar su ORM. Por algo a\u00fan est\u00e1 versi\u00f3n <i>alpha <\/i>\ud83d\ude00 As\u00ed que <i>let&#8217;s rubber ducking!<\/i>.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">El presente<\/h2>\n\n\n\n<p>Actualmente <a href=\"https:\/\/github.com\/vendimia\/vendimia\">Vendimia<\/a> tiene un ORM similar a <a href=\"http:\/\/rubyonrails.org\/\">Rails<\/a>:&nbsp;una&nbsp;clase inicialmente vac\u00eda que extiende a <code>Vendimia\\ActiveRecord\\Record<\/code>, y las relaciones se definen creando variables est\u00e1ticas <code>$belongs_to<\/code>, <code>$has_one<\/code>, y <code>$has_many<\/code> dentro de la clase modelo. En su primera versi\u00f3n le hab\u00eda creado varios m\u00e9todos m\u00e1gicos tipo <a href=\"https:\/\/www.djangoproject.com\/\">Django<\/a>&nbsp;como:<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">namespace products;\n\n$products = models\\product::find_where_name_contains(\"keyboard\");<\/pre>\n\n\n\n<p><\/p>\n<\/div><\/div>\n<\/div><\/div>\n<\/div><\/div>\n\n\n\n<p>Lo que genera un <code data-enlighter-language=\"sql\" class=\"EnlighterJSRAW\">SELECT `products_product`.* FROM `products_product` WHERE (`name` LIKE \"%keyboard%\");<\/code>, y devuelve un objeto <code>Vendimia\\ActiveRecord\\RecordSet<\/code> con cada registro del resultado.<\/p>\n\n\n\n<p>En la segunda iteraci\u00f3n del ORM no le implement\u00e9 dichos m\u00e9todos m\u00e1gicos, en parte por flojera, y en parte porque&nbsp;<em>no son buenas pr\u00e1cticas de programaci\u00f3n&nbsp;<\/em>(los IDE no los identifican, pueden causar confusi\u00f3n, etc.). Tengo pensado crear un <code>trait<\/code> para selectivamente a\u00f1adir a un modelo los m\u00e9todos m\u00e1gicos.<\/p>\n\n\n\n<p>Actualmente no realiza validaci\u00f3n de los valores de los campos. Vendimia no conoce los campos de la tabla (a pesar que si los conoce, lee m\u00e1s adelante), ni su tipo. Si a\u00f1ades una propiedad de la clase cuyo campo no existe en la db, ser\u00e1 el SQL quien genere una excepci\u00f3n.<\/p>\n\n\n\n<p>A mi no me molesta mucho eso, pues funciona bien. Pero en un momento surgi\u00f3 un proyecto web donde tuve que guardar una lista de idiomas por cada participante. Usualmente solo es un idioma, pero puede haber participantes con dos (o m\u00e1s). Y me pareci\u00f3 demasiado <i>overkill <\/i>tener una tabla para guardar los idiomas, en especial por que solo usa dos caracteres para cada idioma.<\/p>\n\n\n\n<p>Al final us\u00e9 un simple campo <code>string<\/code>, y al obtener un registro de participante, le hago un <code>explode<\/code>. Y pens\u00e9 que ser\u00eda bonito que Vendimia haga eso autom\u00e1gicamente, asi como otros tipos de conversi\u00f3n de datos desde la db (Los campos <code>date<\/code> a un objeto <code>Vendimia\\DateTime<\/code>, por ejemplo). Para ello necesito que el modelo sepa el tipo de cada campo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">El problema<\/h3>\n\n\n\n<p>El problema es que&nbsp;Vendimia tiene una&nbsp;forma de crear y actualizar la estructura de la base de datos,&nbsp;<em>ergo tiene informaci\u00f3n sobre&nbsp;los campos de un modelo<\/em>. Una definici\u00f3n de una tabla tiene esta forma (guardada en un fichero <code>apps\/ventas\/db\/venta.php<\/code>):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">namespace ventas\\db;\n\nuse Vendimia\\Database\\Tabledef;\nuse Vendimia\\Database\\Field;\n\nclass venta extends Tabledef\n{\n    var $fecha_creacion = Field::DateTime;\n\n    var $tipo = [Field::FixChar, 1,\n        'index' => true,\n    ];\n\n    var $serie = [Field::SmallInt,\n        'index' => [\n            'unique' => false,\n        ]\n    ];\n\n    var $numero = [Field::Integer,\n        'index' => [\n            'unique' => false,\n        ],\n    ];\n\/\/ ...\n<\/pre>\n\n\n\n<p>Pero esta estructura la he creado completamente aislada del modelo, enfocada \u00fanicamente a la base de datos. A parte, los campos son simples constantes, que el motor de la base de dato lo convierte al nombre correspondiente para la base de datos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">El problema #2<\/h3>\n\n\n\n<p>Este es un problema peque\u00f1o: el concepto de &#8216;modelo es la base de datos&#8217; en un sistema MVC que hizo popular Django y Rails <i>est\u00e1 mal<\/i>. Creo que un <i>refactoring<\/i> del ORM de Vendimia tambi\u00e9n presenta una oportunidad para colocarlo en otro lado, y las clases dentro de <code>models<\/code> ser\u00edan los <i>services<\/i> y\/o los <i>domain objects<\/i> de un MVC real. A mi parecer, esto ser\u00eda suficiente separaci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">El problema #3<\/h3>\n\n\n\n<p>Este problema se llama &#8216;PHP&#8217;. A pesar que sus objetos han mejorado bastante en los \u00faltimos a\u00f1os, y son m\u00e1s elaborados que los de Python o Ruby, aun le falta mucha de la flexibilidad de estos dos para implementar cosas interesantes.<\/p>\n\n\n\n<p>Un punto en particular: PHP no tiene <i>setter<\/i> o <i>getters&nbsp;<\/i>bonitos como tiene Python (llamados&nbsp;<a href=\"https:\/\/docs.python.org\/3.3\/howto\/descriptor.html\"><span style=\"text-decoration: underline;\"><em>Descriptors<\/em><\/span><\/a>), para poder procesar la informaci\u00f3n que se guarda en cada propiedad a trav\u00e9s de un objeto, sin recurrir a m\u00e9todos m\u00e1gicos (que es lo que actualmente hago, pero no para procesar, s\u00f3lo para asignar). Ya hablamos sobre los&nbsp;problemas de los m\u00e9todos m\u00e1gicos, y tambi\u00e9n tengo la intenci\u00f3n de liberarme de la mayor\u00eda de ellos.<\/p>\n\n\n\n<p>Esto tambi\u00e9n trae el problema que no puedo definir&nbsp;el objeto que referencia al campo como una variable simple, por que al asignarle un valor despu\u00e9s, simplemente se borrar\u00eda el objeto. Y usar <em>mutators<\/em> y <em>accessors<\/em>&nbsp;como <code>getName()<\/code> no me parecen muy&nbsp;elegantes y\/o simples (los dos pilares de Vendimia \ud83d\ude09 )<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El futuro (con una&nbsp;soluci\u00f3n)<\/h2>\n\n\n\n<p>Entonces, ahora el ORM de Vendimia estar\u00e1 en su propio <em>namespace<\/em>, distinto del modelo. El ORM tambi\u00e9n servir\u00e1 para obtener los campos para crear la tabla en la base de datos. Usar\u00e1&nbsp;<em>annotations<\/em> para definir&nbsp;los par\u00e1metros de cada campo. Un prototipo de declaraci\u00f3n ser\u00eda:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">namespace products\\orm;\n\nuse Vendimia\\ORM;\nuse Vendimia\\ORM\\Record;\n\nclass product extends Record\n{\n    \/**\n    * @V:ORM type ORM\\Char 8\n    *\/\n    private $name;\n\n    \/**\n    * @V:ORM type ORM\\Char 32\n    * @V:ORM index\n    *\/\n    private $barcode;\n\n    \/**\n    * @V:ORM type ORM\\Decimal 8, 2\n    *\/\n    private $price;\n\n    \/**\n    * @V:ORM type ORM\\Boolean\n    * @V:ORM default `True`\n    *\/\n    private $tax_affected;\n\n    \/**\n    * @V:ORM type ORM\\Array\n    * @V:ORM valid_values \"PCIE\", \"USB&amp;lt;2\", \"FM2\"\n    *\/\n    private $requires;\n\n    \/**\n    * @V:ORM type ORM\\DateTime\n    *\/\n    private $create_at;\n\n}<\/pre>\n\n\n\n<p>De esta forma, los campos estar\u00e1n disponibles para autocompletaci\u00f3n de las IDEs, para validad los valores que se coloquen desde el c\u00f3digo, para formatearlos correctamente cuando vayan o venga desde o hacia la base de datos, y para crear la estructura de la tabla en ella.<\/p>\n\n\n\n<p>El declarar los campos &#8216;<code>private<\/code>&#8216; depende de si al final sigo usando m\u00e9todos m\u00e1gicos, o no.<\/p>\n\n\n\n<p><strong>Pros de usar m\u00e9todos m\u00e1gicos:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Puedo hacer\u00a0<em>lazy evaluation<\/em>, y\u00a0s\u00f3lo ejecutar el <em>query<\/em> cuando\u00a0se intenta acceder a una propiedad\u00a0(como funciona hoy). De no usarlos, habr\u00eda que expl\u00edcitamente solicitar la obtenci\u00f3n de los valores de la base de datos, quiz\u00e1s ejecutando un <code>->fetch()<\/code> al final de la cadena de m\u00e9todos de consulta.<\/li><li>Puedo grabar s\u00f3lo los campos que han sido modificados. No estoy seguro si eso tiene una ventaja de velocidad, pero en el log de la DB se ve m\u00e1s elegante \ud83d\ude42<\/li><li>Puedo validar en ese instante el valor que se asigna a una variable.<\/li><li>Puedo ejecutar <em>setters<\/em>\u00a0personalizados al colocar un valor.<\/li><\/ul>\n\n\n\n<p><strong>Contras de usar m\u00e9todos m\u00e1gicos<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Para que funcionen, las propiedades deben ser inaccesibles, por lo que&nbsp;tendr\u00eda que declararlas como <code>private<\/code> (como en el ejemplo). Pero si modificas una propiedad en un&nbsp;m\u00e9todo <em>dentro<\/em> de la clase, no se ejecutar\u00e1n los <em>getters<\/em> y <em>setters&nbsp;<\/em>de dicha propiedad.<\/li><\/ul>\n\n\n\n<p>Por ahora, creo que lo implementar\u00e9 los getters y setters con m\u00e9todos m\u00e1gicos y quizas un m\u00e9todo para\u00a0usarlo dentro de la clase misma.<\/p>\n\n\n\n<p>El tag <code>@V:ORM<\/code> es el <i>trigger<\/i> que usar\u00e1 el parser para analizar la <em>annotation<\/em> y sacar los valores que anteriormente se almacenaban directamente\u00a0en un array. Ser\u00e1 necesario\u00a0guardar una <i>cache<\/i><em>\u00a0<\/em>del array generado.<\/p>\n\n\n\n<p>Un problema de esta aproximaci\u00f3n es que&nbsp;el elemento &#8216;type&#8217; debe ser una clase que implemente una interface&nbsp;definida. El problema viene en la resoluci\u00f3n del FQCN del mismo,&nbsp;tomando en cuenta el <code>namespace<\/code> actual, y los aliases definidos con <code>use<\/code>. El <i>keyword<\/i> <code>::class<\/code> s\u00f3lo funciona en tiempo de compilaci\u00f3n y con nombres de clases (aunque no existan). No existe una funci\u00f3n similar para obtener el FQCN de un string en tiempo de ejecuci\u00f3n, <a href=\"http:\/\/stackoverflow.com\/questions\/43351573\/build-fqcn-from-a-string\">y no veo una forma de poder implementarlo<\/a>.<\/p>\n\n\n\n<p>Para&nbsp;solventar ello, se me ocurre buscar una clase dentro de un lugar fijo en Vendimia si el nombre de la clase empieza con <code>ORM\\<\/code> (ignorando el alias), y usar una clase particular si el nombre empieza con un <code>\\<\/code>.<\/p>\n\n\n\n<p>\u00bfComentarios? \u00bfSugerencias?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>No he tenido tiempo de escribir sobre mi framework de desarrollo web Vendimia. Y ahora he tenido una idea para<\/p>\n","protected":false},"author":1,"featured_media":159,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[14,15],"tags":[],"class_list":["post-139","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php","category-vendimia_framework"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/drmad.org\/blog\/wp-content\/uploads\/2017\/05\/datacenter.jpg?fit=1920%2C672&ssl=1","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p6p3z1-2f","jetpack-related-posts":[],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/posts\/139","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/comments?post=139"}],"version-history":[{"count":25,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/posts\/139\/revisions"}],"predecessor-version":[{"id":833,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/posts\/139\/revisions\/833"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/media\/159"}],"wp:attachment":[{"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/media?parent=139"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/categories?post=139"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/drmad.org\/blog\/wp-json\/wp\/v2\/tags?post=139"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}