#+TITLE: Duckbrain - Manual de inicio #+AUTHOR: KJ #+OPTIONS: #+SETUPFILE: ./readtheorg_inline.theme * Introducción [[https://git.kj2.me/kj/duckbrain][Duckbrain]] es un conjunto de librerías diseñado para hacer algo simple que cualquiera puede deshacer y rearmar fácilmente. Gracias a su versatilidad, puedes usarlo en cualquier proyecto, pero ten en cuenta que a medida que el proyecto sea más grande, también lo será la ingeniería necesaria de tu parte para adaptarlo a los requisitos. Otro punto a tener en cuenta es que Duckbrain puede ser muy útil si estás comenzando, ya que su pequeñez y modficabilidad hacen que sea más fácil de comprender y, a medida que avances, te ayudará a entender cosas más grandes. ** Estructura de archivos de Duckbrain Al entrar a la carpeta de Duckbrain encontrarás esta estructura de archivos (ignoramos el readme.org por obvias razones). #+begin_src ditaa . ├── config.php ├── index.php ├── .htaccess └── src ├── Controllers ├── Libs │   ├── Database.php │   ├── Middleware.php │   ├── Model.php │   ├── Neuron.php │   ├── Request.php │   ├── Router.php │   └── View.php ├── Middlewares ├── Models ├── Routers └── Views #+end_src Primero, observemos las carpetas y luego los archivos: *** Carpeta src En esta carpeta se encuentra todo el código PHP de lo que será nuestro proyecto. Es posible cambiarle el nombre/ruta a gusto, como te darás cuenta más adelante. *** Carpetas: Controllers, Libs, Models, Middlewares, Views y Routers Si sabes de estructuras de diseño de software, ya entenderás qué va en cada una de estas carpetas. Si no, es momento de investigar por lo menos lo que significa la arquitectura MVC, ya que es una arquitectura simple bastante común y lo que usaremos en este manual. Al igual que con la carpeta =src=, puedes cambiar y organizar las carpetas según tus necesidades. Solo ten cuidado de modificar los archivos asociados a las carpetas Views y Router, ya que afectarían al archivo =src/Libs/View.php= y el archivo =index.php=, respectivamente. En todo caso, mejor no hagas cambios en las rutas carpetas antes de completar po lo menos la sección [[Mi primera aplicación][Mi primera aplicación]]. Si eres un ansias y ya has programado antes en alguna arquitectura de diseño como DDD, MVC, TDD, EDD, etc. y manejas decentemente PHP, con que te leas el código del archivo =index.php= (son solo 20 líneas de código) te deberá bastar y puedes saltarte el resto del este manual y guiarte de ahora en adelante con la documentación de los comentarios (docblocks) de las librerías. *** Los archivos en la raíz El =.htaccess= es un archivo que te será útil si usas [[https://httpd.apache.org/][Apache]] o algún otro servidor similar. Contiene una configuración especial para que funcionen las *rutas virtuales*, o sea, para que podamos crear una ruta tipo =misitio.com/mi-ruta-virtual/= a pesar de que la carpeta =mi-ruta-virtual= no exista. En caso de que uses nginx, debes configurar el webserver con la regla equivalente al contenido del archivo =.htaccess= que sería más o menos esto: #+begin_src nginx location / { try_files $uri $uri/ ./index.php$args; } #+end_src El archivo =index.php= es el punto de entrada de nuestra aplicación, aquí sucederá toda la magia inicial y sólo requiere de 20 míseras lineas para hacer ese gran trabajo y soportar sistemas tan grandes sin despeinarse. El archivo =config.php=, como su nombre indica, es el archivo para colocar las configuraciones. *** Los archivos en la carpeta Libs Los archivos en la carpeta "Libs" son principalmente librerías. Cada archivo tiene un nombre descriptivo que indica su función. Si tienes conocimientos básicos sobre diseño y lees los nombres de estas librerías, deberías poder comprender lo que hacen. Si no lo entiendes con solo eso, sigue leyendo el manual que hablaremos de casi todas poco a poco. ** Comprendiendo el arranque del sistema Como la intención de este manual es que comprendas [[https://git.kj2.me/kj/duckbrain][Duckbrain]] y no solo seas un robot que copia y pega de la documentación, vamos a calentar comprendiendo el código que lo arranca. Como ya mencionamos antes, el archivo =.htaccess= se encarga de hacer funcionar las rutas virtuales, pero eso como programador que eres te debería hacer saltar una pregunta "¿Cómo es que hace eso?" y la respuesta es bastante simple: Supongamos que entramos en la ruta =/hola= (o sea, =misitio.com/hola=), lo que hará el =.htaccess= será lo siguiente: Primero intenta comprobar si el fichero "hola" existe en la ruta a la que se desea acceder, si no es el caso prueba si existe una carpeta "hola" y si tampoco sucede eso, reenvía todo a =index.php= y le pide que él se encargue de ahí en adelante. Si llega hasta la tercera opción decimos que es una *ruta virtual* (lo que mencionamos en [[Los archivos en la raíz][Los archivos en la raiz]]), porque devolveremos algo desde el PHP simulando un archivo o una carpeta, a pesar de que esa ruta realmente no existe ni como fichero ni como carpeta. Ahora veamos que hace =index.php=, en la primera línea nos dice esto: #+begin_src php require_once('config.php'); #+end_src Aquí carga el archivo =config.php=, o sea, carga la configuración. Para esta parte del manual, solo nos interesa las 2 últimas líneas ya que todo lo demás en realidad no está configurando nada aún. Lo que dice las líneas es esto: #+begin_src php define('ROOT_DIR', __DIR__); define('ROOT_CORE', ROOT_DIR.'/src'); #+end_src Se está definiendo una constante =ROOT_DIR= que contiene como valor el directorio en donde está el archivo =config.php=, que también sería la raíz de [[https://git.kj2.me/kj/duckbrain][Duckbrain]]. La siguiente línea también es la definición de una constante: =ROOT_CORE= lo que hace es tener la ruta de la carpeta =src=. Hasta aquí acabamos con la definición de constantes, ahora el bloque que viene es donde se poner lo bueno: #+begin_src php spl_autoload_register(function ($className) { $fp = str_replace('\\','/',$className); $name = basename($fp); $dir = dirname($fp); $file = ROOT_CORE.'/'.$dir.'/'.$name.'.php'; if (file_exists($file)) { require_once $file; return; } }); #+end_src Este bloque define un autoloader para PHP. Como su nombre lo indica, lo que hace es cargar los archivos en función del su clase, considerando siempre que dichos archivos estarán dentro de la carpeta configurada en =ROOT_CORE=, que generalmente será =src=. Si aún no te ha hecho clic lo bonito que es tener un autoloader como este, básicamente lo que sucede es que en lugar de que a cada archivo que uses lo tengas que cargar usando la función =include()=, =require()=, =include_once()= o =require_once()=, bastará solo con usar la clase que contiene, siguiendo la convención de que el =namespace= indica las carpetas y el nombre del archivo tiene el mismo nombre de la clase. Si aún no lo entendiste, no pierdas tiempo releyendo el párrafo anterior, en seguida lo vamos a retomar y a explicar mejor. Por lo pronto vamos a por el bloque final: #+begin_src php $routers = glob(ROOT_CORE.'/Routers/*.php'); foreach($routers as $file){ require_once($file); } \Libs\Router::apply(); #+end_src La primera línea de código lista todos los archivos =.php= dentro de nuestra carpeta =src/Routers/=, la segunda parte los incluye todos esos archivos PHP, o sea, ejecuta el código PHP que tengan dentro y la última llama a la clase =\Lib\Router::apply()= y aquí hacemos uso del bloque anterior de la siguiente manera: Hasta ahora, el único archivo que hemos cargado es el de configuración en la primera línea. La clase =\Libs\Router= realmente no está definida y en un PHP normal nos daría error por ello, pero aquí viene al rescate nuestro autoloader, ya que el toma la clase y como se llama =\Lib\Router= comprende que tiene que intentar cargar el archivo en =src/Libs/Router.php= que es donde vendría a estar la clase que estamos necesitando. Como el archivo existe y la clase está definida allí, es capaz de cargarla y de correr la función estática =apply()= y así es como todos terminan felices sin dar ningún error :). Nótese que lo que ha hecho el autoloader es convertir los backslashes o barras invertidas en slashes o barrar normales y luego concatenarlas con =ROOT_CORE= para obtener la ruta de nuestro archivo. Esto es muy importante, así que si no lo entiendes léelo nuevamente (explicación y código) o pide a algún amigo, conocido o random de internet en algún foro o chat de telegram, matrix, steam, discord, reddit, etc. que te lo explique hasta que comprendas perfectamente ese fracmento de código. ** Extra: Namespaces Si eres nuevo en esto puede que no sepas lo que es un namespace, pero si también te dio curiosidad el que la una clase tenga =\= (backslash) en su nombre, entonces tienes futuro en este campo y como no quiero que te vayas de este manual, voy a explicarte rápidamente de donde viene ese backslash. Si abres el archivo =src/Libs/Router.php= verás que en su primera línea dice esto: #+begin_src php namespace Libs; #+end_src Esa línea generalmente será la primera siempre de un archivo PHP que use namespaces, no puede haber ninguna otra línea de código. Lo que hace dicha línea es darle una "familia" a la clase y con dicha familia, también viene el apellido de la misma. De modo que cualquier clase que se cree dentro de un archivo con un namespace, de ahora en adelante tendrá como "apellido" ese namespace en su nombre. De ese modo, como en el archivo Router declaramos la clase =Router=, en realidad estamos creando la clase =Libs\Router= (el backslash del inicio se puede omitir). Puede que ahora mismo no notes la utilidad de esto, pero es algo muy útil realmente y ya verás más adelante la magia que supone. * Primeros pasos Antes de ponernos a programar vamos a asegurarnos de estar en la misma pagina preparando nuestro entorno de trabajo y haciendo el usual Hola mundo. ** Usando un Webserver para el desarrollo Desde luego esta la carpeta con [[https://git.kj2.me/kj/duckbrain][Duckbrain]] supongo que ya la tienes con un webserver nginx o apache en localhost. Si no es el caso, al menos deberás instalarte PHP primero y una ves instalado, abres una terminal en la carpeta donde tienes Duckbrain y ejecutas: #+begin_src bash php -S localhost:80 #+end_src Eso te creará un webserver que podrás acceder escribiendo en el navegador =http://localhost= y verás lo que estemos corriendo en el proyecto. En la terminal, ahora verás los logs de acceso y en caso de que tu código tenga un error, igual aparecerá allí. En el caso de Linux, MAC y FreeBSD los accesos se verán en amarillo para errores no fatales como archivos inexistente y en rojo si es un error fatal que detiene la ejecución de PHP. En Windows no estoy seguro. He conocido gente que usa directamente un hosting y edita los archivos allí. Eso no está mal si estás aprendiendo o para hacer código rápido, pero en lo personal, siempre recomiendo desarrollar en local, ya que es más rápido y con herramientas como Docker o KVM puedes simular la misma configuración que tendría un servidor en producción, con los retoques que necesites para tu entorno de desarrollo. ** Hola mundo Agarren su teclado y su editor de código que vamos a comenzar a hacer la magia. Como es usual, el primer paso para hacer nuetra aplicación, será imprimir un ~hello world~. Para ello vamos a la carpeta =src/Routers= y crearemos un archivo con el nombre de =main.php= y dentro colocaremos el siguiente código: #+begin_src php Ignota - Notas encriptadas punto a punto
Configuración de expiración
#+end_src No creo que sea necesario que expliquemos un código html, si no lo entiendes, no te quedes con la duda y busca comprenderlo/aprenderlo. ** Mostrando el formulario Ahora no vamos a hacer un archivos completos, los haremos a pedacitos de código a medida que avancemos. Comenzando con el controlador, vamos a crear un archivo llamado =NoteController.php= en la carpeta =src/Controllers/= y colocaremos dentro este código: #+begin_src php content = openssl_encrypt( $this->content, 'AES-256-CBC', $secretKey, OPENSSL_RAW_DATA, substr($secretKey, 0, 16) ); // Devolvemos la llave return $secretKey; } #+end_src Con esto podemos encriptar la nota antes de guardarla y nos devolverá una llave aleatoria con la que habrá encriptado el texto. Ahora vamos a hacer un controlador que recibirá el formulario, encriptará la nota para luego guardarla en la base de datos y finalmente redirigirá al enlace de la nota que llevará en la URL la llave de encriptado, para ello añadiremos esta función a la clase =NoteController=: #+begin_src php public static function create(Request $request): void { // Creamos una instancia de Note y le pasamos los datos del formulario $note = new Note; $note->content = $request->post->note; $note->max_views = $request->post->max_views; $note->expire_at = strtotime("+{$request->post->expiration} minutes"); // Encriptamos los datos y luego los guardamos $secretKey = $note->encrypt(); $note->save(); // Redirigimos a la url final donde se verá la nota Router::redirect("/{$note->id}/{$secretKey}"); } #+end_src Desde luego, también tenemos que añadir los uses de =Libs\Router= y =Models\Note=: #+begin_src php use Libs\Router; use Models\Note; #+end_src Si te sientes permido y no sabes dónde poner esas 2 líneas, es arriba de la línea que comeniza con =class=, de hecho ahí verás otras líneas similares. Para finalizar vamos configurar la ruta. Así que editamos el archivo =note.php= que está en =src/Routers/= y le añadimos: #+begin_src php Router::post('/', [NoteController::class, 'create']); #+end_src Esta vez, como la petición viene de un formulario que la enviará mediande el método HTTP POST, al momento de configurar nuestra ruta, en lugar de usar el =Router::get= que hemos estado usando hasta ahora, usamos =Router::post=. Ahora ya podemos comenzar a crear nuestras notas, auque aún no las podremos ver. ** Recta final: Desencriptando y mostrando las notas Vamos directo al grano, editaremos el modelo (=src/Models/Note.php=) y le añadiremos esta otra función para desencriptar: #+begin_src php public function decrypt(string $secretKey) : void { // Verificamos si la nota ha expirado if ($this->expire_at < time()) { $this->content = 'La nota se ha destruido por expiración.'; $this->delete(); return; } // Intentamos desencriptar. $content = openssl_decrypt( $this->content, 'AES-256-CBC', $secretKey, OPENSSL_RAW_DATA, substr($secretKey, 0, 16) ); // Verificamos si desencriptó correctamente if (is_string($content)) { // Verificamos si está configurado para infinitas vistas. if ($this->max_views == 0) { $this->content = $content; return; } // Reducimos el contador de vistas $this->max_views = $this->max_views - 1; $this->save(); // Verificamos si el contador de vistas ha llegado a 0. if ($this->max_views <= 0) { $this->content = 'La nota se ha destruido según su límite de vistas.'; $this->delete(); return; } // Si no ha expirado, solo devolvemos el contenido $this->content = $content; } else { // Si no se pudo desencriptar colocamos un error $this->content = 'Llave incorrecta.'; } } #+end_src Ahora en el =NoteController= añadiremos esta otra función: #+begin_src php public static function show(Request $request): void { // Verificamos si el ID es un número if (!is_numeric($request->params->id)) { Router::defaultNotFound(); return; } // Obtenemos la nota de la base de datos $note = Note::getById($request->params->id); // Verificamos si la nota existe if (is_null($note)) { Router::defaultNotFound(); return; } // Desencriptamos la nota $note->decrypt($request->params->key); // Imprimimos en formato de texto plano el resultado del desencriptado $view = new View; $view->text($note->content); } #+end_src En este código hay 2 cosas nuevas de [[https://git.kj2.me/kj/duckbrain][Duckbrain]] que aún no hemos visto: La primera serían los atributos =$request->params->id= y =$request->params->key= que propablemente te preguntes de dónde salen. Pero aún es momento de que te los explique, por lo que ten paciencia que no fatla mucho. La segunda cosa es el método =text= de la librería =View=: Este método lo que hace es imprimir en texto plano lo que le enviemos, dicho de otro modo, en lugar de devolver el usual documento html que suelen cargar todas lás páginas, simulará que estamos abriendo un archivo =.txt=. Ahora la cerecita del paste: Vamos a configurar el router, así que editamos nuestro archivo =src/Views/note.php= y le añadimos la siguiente línea: #+begin_src php Router::get('/{id}/{key}', [NoteController::class, 'show']); #+end_src Ahora notarás que esta vez tiene algo singular que no hemos visto antes y es que en la ruta hay textos encerrados entre llaves. Lo que eso quiere decir es que el texto allí puede ser arbitrario y que lo que pongas allí se guardará en una variable y si ya uniste las líneas esas variables luego podremos accederlas mediante =$request->params->id= y =$request->params->key= respectivamente (llevan el mismo nombre alfinal por algo). Por si aún no se entiende, habamos un ejemplo: Supongamos que entramos mediante la url: =http://localhost/52/urE44g4we4r14daFifG= entonces el valor de =$request->params->id= sería =52=, mientras que el de =$request->params->key= sería =urE44g4we4r14daFifG=. Si aún no lo entiendes, te recomiendo testearlo cambiando esos valores e imprimiendo los valores de esas variables en el método =show= de =NoteController=. Y con eso ya hemos terminado, ya puedes probar la aplicación, intentar romperla, etc. Ya puedes comenzar a hacer tus propias aplicaciones, no tienes ninguna idea de qué hacer, te reto a hacer lo siguiente: Crea una aplicación de gestión tareas (un TODO list), en donde puedas agregar tareas, marcarlas como finalizadas, en espera o canceladas. Si te parece sencillo hasta ahí, añade que el sistema tendrá usuarios que deberán loguearse, cada uno tendrá sus propias listas de tareas que solo ellos pueden ver cuando se loguean. ** Repositorio Por si alguien se perdió o sencillamente quiere ver código, he subido la aplicación ignota en este repositorio: https://gitlab.com/kj2me/Ignota Te animo a hacerle un fork, mejorarlo y enviármelo. Los forks que me parezcan interesantes, los colocaré como destacados en el repositorio original para que otros puedan verlos. * Contacto Como siempre, pueden contactarme mediante el correo =webmaster@outcontol.net= También pueden unirse a mi comunidad de discord: https://discord.gg/p7TAAnTJPK No es una comunidad de programación solamente, pero ahora mismo no existe una de solo Duckbrain.