Tutorial: Desarrolla tu propio blog: Slog
Slog: un blog simple
Introducción
En este artículo mostraré lo fácil que es crear un sistema propio de publicación de bitácora (o weblog, o blog), y lo haré en formato tutorial, con un sencillo ejemplo: Slog. Slog será un proyecto que iremos construyendo poco a poco en este y en los siguiente tutoriales. En este primer tutorial crearemos un Slog básico, sin muchas funcionalidades, con tan sólo visualización de artículos en listado con paginación, visualización de un artículo, envío de comentarios, y autentificación.
En sucesivos tutoriales continuaremos el desarrollo añadiendo más funcionalidades, como un editor WYSIWYG, un panel de administración, moderación de comentarios, permalinks, estadísticas, pingbacks, blogchat, etc.
Al finalizar la serie de tutoriales Slog, obtendremos un CMS muy completo que nada tendrá que envidiar a Wordpress.
Conocimientos previos
Para seguir el tutorial es necesario tener buenos conocimientos de PHP5 y de la OOP (o Programación Orientada a Objetos), además de tener al menos algunas nociones de XHTML, CSS, y SQL. Y por supuesto, se debe disponer de un servidor web, ya que explicar cómo instalar un servidor web se escapa de la intención de este tutorial.
Herramientas
Usaremos PHP5 como lenguaje de script (no PHP4, ya que usaremos la capacidad OOP de PHP5), MySQL como gestor de base de datos, Apache (aunque valdrá cualquier otro), y un editor de texto, a ser posible un IDE como Zend Studio, o Eclipse.
Usaremos además la arquitectura MVC, algo reducida para que no sea muy complejo. Más adelante completaremos un MVC completo. Para el que no sepa qué es MVC, más adelante explicaré resumidamente cómo funciona la arquitectura MVC.
¿Qué queremos hacer?
Esta es la primera pregunta que nos debemos hacer antes de empezar a trabajar en un proyecto. Hay que definir lo más detalladamente qué es lo que se quiere conseguir. Después vendrán más preguntas, como ¿cómo lo hacemos? ¿cuánto tiempo nos llevará? ¿cuánto dinero costará? Etc.
Pues bien, queremos hacer una web que tenga básicamente tres páginas:
- Listado de artículos
- Visualización de un artículo
- Edición de artículos
Para poder acceder a ciertas secciones (edición de artículos), el sistema deberá autentificar usuarios. Por lo tanto, nos hará falta una página más con un formulario de login.
Arquitectura MVC
La arquitectura MVC consiste básicamente en separar en una aplicación los datos, las vistas, y el código, obteniendo el Modelo, la Vista, y el Controlador, respectivamente.
Modelo
Un modelo es la parte de la aplicación que maneja la información. En nuestro caso, modelos serán el de usuarios, el de artículos, y el de comentarios. Cada modelo es un objeto, y contiene los métodos necesarios para manejar toda la información que la aplicación necesite.
Los controladores sólo podrán conseguir y almacenar información a través de los modelos.
Vista
La vista es la parte de presentación de la aplicación. En desarrollo web ésta se compone de plantillas (XHTML, XML, CSS, imágenes...).
Un controlador sólo puede mostrar información a través de las vistas.
Controlador
El controlador podría considerarse como el código real y limpio de la aplicación, puesto que en las Vistas el código es casi inexistente, tan sólo limitado a control de flujo, y en los Modelos el código se limita a obtener y almacenar datos de la aplicación a la base de datos.
El controlador obtiene datos de los modelos, y los envía a las vistas.
Los controladores a su vez se dividen en acciones. Un controlador puede realizar varias acciones, y cada acción tendrá su propia vista. Por ejemplo, tenemos un controlador blog, que puede realizar las acciones: list, view, y edit. A cada acción le corresponde su vista, list para listar los artículos del blog, view para ver un artículo completo, y edit para editar un artículo o crear uno nuevo.
MVC en Slog
En Slog no utilizaremos modelos, al menos en un principio, para no hacer demasiado complejo el tutorial. Por lo tanto, consideraremos que los modelos están dentro de los controladores.
El modelo de datos
Cuando y sólo cuando ya sabemos qué queremos hacer, es hora de crear el modelo de datos.
En este caso, hemos dicho que tendremos usuarios, artículos y comentarios. Detallemos pues las tablas y sus campos.
slog_posts
CREATE TABLE `slog_posts` ( `id_post` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id_user` int(10) UNSIGNED NOT NULL, `title` varchar(255) NOT NULL, `body` longtext NOT NULL, `date` int(10) UNSIGNED NOT NULL, PRIMARY KEY (`id_post`) );
- id_post: (clave primaria) id del artículo
- title: título del artículo
- body: contenido del artículo
- date: fecha de publicación en formato timestamp
- id_user: (clave foránea) id del usuario autor del artículo
slog_users
CREATE TABLE `slog_users` ( `id_user` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `nick` varchar(50) NOT NULL, `email` varchar(150) NOT NULL, `password` varchar(50) NOT NULL, PRIMARY KEY (`id_user`) );
- id_user: (clave primaria) id de usuario
- nick: nick del usuario
- email: email del usuario
- password: password codificada en MD5
slog_comments
CREATE TABLE `slog_comments` ( `id_comment` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id_post` int(10) UNSIGNED NOT NULL, `nick` varchar(100) NOT NULL, `email` varchar(150) NOT NULL, `web` varchar(150) NOT NULL, `body` text NOT NULL, `date` int(10) UNSIGNED NOT NULL, PRIMARY KEY (`id_comment`) );
- id_comment: (clave primaria) id del comentario
- id_post: (clave foránea) id del artículo en el que se comenta
- nick: nick del comentarista
- email: email
- web: página web del autor del comentario
- body: contenido del comentario
- date: fecha de creación
Árbol de directorios
Debemos ahora definir qué estructura tendrá nuestra apliación. Usaremos una arquitectura MVC simple.
- /app
- /controllers
- /controlador1
- accion1.php
- accion2.php
- ...
- /controlador2
- accion1.php
- ...
- ...
- /controlador1
- /views
- /controlador1
- accion1.tpl
- ...
- index.tpl
- /controlador1
- /controllers
- /lib
- mvc.inc.php
- db.inc.php
- mvc.inc.php
- index.php
- style.css
No os asustéis, el árbol de directorios es mucho más complejo que la aplicación en sí.
Podemos apreciar que existen dos directorios en el raíz: /app y /lib. En /app irán los ficheros de nuestro sistema MVC, con /controllers (con un subdirectorio por cada controlador, y un fichero por cada acción) y /views, dividido de igual forma que /controllers. En /views además irá el fichero index.tpl, que es el esqueleto HTML común a todos los controladores, que contendrá las etiquetas HTML, HEAD, y BODY.
En /lib tendremos las librerías que usemos, que de momento son sólo dos: mvc.inc.php con la clase MVC que manejará el sistema MVC, y db.inc.php, el driver MySQL que usaremos.
En index.php inicializaremos la página, instanciaremos las clases MVC y DB, ejecutaremos el MVC, y devolveremos el resultado a la web.
Código fuente de Slog
A continuación explicaré de la forma más detallada posible el código fuente de Slog, aunque lo mejor es que te descargues el código fuente, donde hay cantidad de comentarios explicativos.
La versión de Slog que explicaré aquí será la v0.1.
/index.php
Este es un fichero pequeño, y creo que se comprende todo perfectamente con tan sólo echarle un vistazo. Realiza las siguientes operaciones:
- Inicializa la sesión.
- Define la configuración de la base de datos.
- Añade las librerías mvc.inc.php y db.inc.php.
- Instancia MVC y DB, quedando $db como variable global.
- Se establece el título del blog.
- Ejecutamos el MVC y lo enviamos a la web.
/lib/db.inc.php
Contiene la clase DB, con los siguientes métodos:
- query($sql)
- Ejecuta una sentencia SQL. Si es un INSERT, devolverá el ID del campo autonumérico que se ha creado.
- fetch($sql=null)
- Devuelve un registro (array asociativo) de la consulta ejecutada anteriormente con query($sql).
- Si se especifica un valor en $sql, llamará a query() antes de devolver el registro.
- fetchOne($sql=null)
- Devuelve el primer campo como valor del registro obtenido de la consulta.
- Al igual que fetch(), si se especifica $sql ejecutará la sentencia antes de devolver el campo.
- fetchAll($sql=null)
- Devuelve todos los registros obtenidos de la consulta.
La clase se instancia pasando como parámetro en el constructor un array con la configuración de la base de datos. El array debe tener la estructura siguiente (definido en /index.php):
array con la configuración de la base de datos
'host' => 'localhost', 'name' => 'slog', 'user' => 'root', 'pass' => '' );
Se definen el hostname del servidor de base de datos, el nombre de la base de datos, usuario y contraseña, en host, name, user, y pass, respectivamente.
En /index.php se instancia esta clase en el objeto $db, que quedará en el ámbito de variables en cualquier acción de cualquier controlador.
/lib/mvc.inc.php
Esta librería es la principal, el corazón de nuestro sistema, ya que maneja el sistema MVC. Tomémoslo como una torre de control que procesa la información que se le envía, y llama al controlador/acción adecuado.
Contiene dos métodos: run() y fetch().
- fetch($tpl)
- Devuelve el resultado de ejecutar una plantilla. Por ejemplo, se ejecutará fetch('blog/view') para obtener la plantilla localizada en /app/views/blog/view.tpl.
- En las plantillas se puede acceder a las variables del objeto $mvc (class MVC), referenciado como $this. Por ejemplo el título lo podemos obtener con $this->title, dentro de una plantilla.
- run()
- Guarda en $this->login la estructura con información del usuario que está ingresado en el sistema, o null si el usuario no está registrado (no ha hecho login).
- Guarda en $this->title el título de la página (Slog).
- Obtiene de $_GET['q'] el controlador/acción a ejecutar. Si no existe esa variable, se ejecutará 'blog/list' (listado de artículos). Pero si existe $_GET['p'] ejecutará 'blog/view' (visualización de artículo) usando como id_post el valor de $_GET['p'].
- Mediante require() incluye y ejecuta en el mismo método el controlador/acción obtenido por el paso anterior. Por ejemplo, para ?q=blog/view, aquí incluirá el código /app/controllers/blog/view.php, el cual podrá acceder a las variables del objeto $mvc (class MVC) mediante $this->variable.
- Obtiene el código de los bloques (blog/_recientes.tpl y blog/_slog.tpl) y lo guarda en $this->blocks.
- Ejecuta la plantilla del controlador/acción (/app/views/blog/view.tpl, siguiendo el ejemplo anterior) mediante $this->fetch(), y almacena el contenido en $this->content.
- Finalmente, devuelve la plantilla '/app/views/index.tpl'.
Controladores de la aplicación
Veamos ahora el código de los distintos controladores/acciones de la aplicación. Estos ficheros, recordemos, son ejecutados en el ámbito de variables de MVC, por lo que podemos acceder a sus variables mediante $this->variable. Además, tendremos disponible el objeto $db como variable local.
/app/controllers/blog/list.php
Esta es la acción list del controlador blog. Se ejecutará si la web es llamada mediante http://miblog.com/?q=blog/list, o sencillamente mediante http://miblog.com, ya que este es el controlador/acción usado por defecto.
blog/list.php
$limit = 10; $start = $page * $limit; $sql = " SELECT posts.*, users.nick FROM slog_posts posts JOIN slog_users users ON (users.id_user = posts.id_user) ORDER BY posts.date DESC LIMIT $start, $limit "; $this->posts = $db->fetchAll($sql); $this->page = $page; $this->title = 'Listado de artículos | ' . $this->title;
En la línea #1 obtenemos la página que se quiere visualizar. El valor de la página se nos indica mediante $_GET['page']. En #2 y #3 obtenemos el registro desde donde empezar a obtener articulos y el número máximo de artículos.
#5 creamos la consulta SQL. En #12 ejecutamos la contulta, obteniendo todos los registros posibles, y lo guardamos en $this->posts.
Guardamos (#13) la página actual en $this->page, y anteponemos el título de esta acción en el título de la página (#14).
Tras la ejecución de este código, el MVC ejecutará la vista correspondiente, en este caso /app/views/blog/list.tpl
/app/controllers/blog/view.php
Esta acción es llamada para que se visualice un artículo completo. Es ejecutada cuando se solicita una página tipo http://miblog.com/?p=21, donde 21 es el id_post del artículo a visualizar.
blog/view.php
$sql = " SELECT posts.*, users.nick FROM slog_posts posts JOIN slog_users users ON (users.id_user = posts.id_user) WHERE id_post = $this->id_post "; $post = $db->fetch($sql); if ($post) { $sql = " SELECT * FROM slog_comments WHERE id_post = $this->id_post "; $comments = $db->fetchAll($sql); } $this->title = $post['title'] . ' | ' . $this->title; $this->post = $post; $this->comments = $comments; $this->allowed_tags = '<p><br><a><img><b><i><strong>';
Al llegar a este código, disponemos del id de artículo en $this->id_post (obtenido de $_GET['p']). Ejecutamos una consulta SQL para obtener el registro del usuario (#1, #7), y otra consulta para obtener los comentarios del artículo (#10, #15). Ponemos el título del artículo dentro del título de la página (#18), y guardamos los datos del artículo (#19) y de los comentarios (#20).
A continuación ponemos los tags permitidos para escribir comentarios (#22), y guardamos la estructura de datos devuelta por un envío de comentario anterior (#24), si lo hubo.
Finalmente, se ejecutará la plantilla /app/views/blog/view.tpl
/app/controllers/blog/edit.php
Aquí manejaremos el formulario de edición de noticias.
blog/edit.php
if (!$this->login) { } if ($id_post) { $sql = " SELECT posts.*, users.nick FROM slog_posts posts JOIN slog_users users ON (users.id_user = posts.id_user) WHERE id_post = $id_post "; $post = $db->fetch($sql); } else { 'id_post' => 0, 'title' => 'Nuevo artículo', 'body' => '' ); } $this->allowed_tags = '<p><b><i><strong><a><img><ul><ol><li><pre><code>'; if ($post_id_post == 0) { $sql = " INSERT INTO slog_posts (id_user, title, body, date) VALUES ($id_user, '$title', '$body', $date) "; $id_post = $db->query($sql); } else { if ($id_post == $post_id_post) { $sql = " UPDATE slog_posts SET title='$title', body='$body' WHERE id_post=$id_post "; $db->query($sql); } } } $this->title = 'Edición de ' . $post['title'] . ' | ' . $this->title; $this->post = $post;
Es importante que sólo los usuarios registrados puedan editar noticias, y por eso al principio se redirecciona a la página de login (#1) si quien accede a esta página no está registrado en el sistema.
Obtenemos el id del artículo (#6) y la información de éste (#8-#21) si es que existe ($id_post != 0).
Si estamos ante un envío de formulario (#26), procesamos los datos, y hacemos INSERT si el artículo es nuevo (#31-#38), o UPDATE si es una modificación de artículo (#40-#46).
Finalmente modificamos el título de la página (#53) y guardamos la información del artículo en $this->post (#54).
Se ejecutará la vista blog/edit.tpl, donde está el formulario de edición.
/app/controllers/blog/comment.php
Esta acción no tiene ninguna vista, ya que se limita únicamente a procesar el formulario de nuevo comentario enviado desde blog/view.
blog/comment.php
$this->allowed_tags = '<p><br><a><img><b><i><strong>'; $comment_message = 'Debes poner un nombre.'; } $comment_message = 'Debes poner tu email (no será publicado).'; } $comment_message = 'Debes poner algo en el comentario'; } if (!$comment_message) { $sql = " INSERT INTO slog_comments (nick, email, web, body, id_post, date) VALUES ('$nick', '$email', '$web', '$body', $id_post, $date) "; $id_comment = $db->query($sql); } else { 'message' => $comment_message, 'nick' => $nick, 'email' => $email, 'web' => $web, 'body' => $body ); } }
Se obtienen las variables del formulario (#4-#9), se hacen las comprobaciones pertinentes de validación (#12-19), y si no hubo ningún error se inserta el nuevo comentario en la base de datos (#22-27), redirigiendo a la visualización del artículo, con ancla en el comentario recién incluido (#28).
Si hubo algún error, se almacena una estructura con los datos recibidos del formulario en la variable de sesión $_SESSION['comment_info'] (#30), y se redirige a la vista del artículo, con ancla en el formulario de envío de comentario (#37).
/app/controllers/user/login.php
En esta acción se procesa el formulario de login, autentificando al usuario en el sistema.
user/login.php
$referer = '?q='; } $_SESSION['referer'] = $referer; } $sql = " SELECT id_user, nick, email FROM slog_users WHERE email = '$email' AND password = MD5('$passowrd') "; if ($user = $db->fetch($sql)) { $_SESSION['user'] = $user; $referer = $_SESSION['referer']; } else { $this->login_message = 'Email o contraseña incorrectas'; } } $this->title = 'Login | ' . $this->title;
Se obtiene la url a la que hay que regresar tras hacer login (#1-#7). Se procesan los datos del formulario (#9-#11), y se comprueba que el usuario y contraseña son corectos (#13-#19), creando la variable de sesión $_SESSION['user'] con el registro del usuario (#20) y redirigiendo a la página anterior, si todo fue bien. O mostrará un mensaje de error (#27) volviendo a la página de login si no hubo éxito en la autentificación.
/app/controllers/user/logout.php
Simplemente se produce la salida del sistema del usuario.
user/logout.php
$referer = '?q='; }
Se opbtiene la url a la que volver (#1-#4), se elimina la variable de sesión del usuario (#6), y se vuelve atrás (#7).
Slog: un blog online
Slog está online, para que podáis ver cómo queda y probarlo escribiendo y modificando artículos, y enviando comentarios en los artículos (usuario admin, password admin). Recordad que esta es una primera versión cuya única finalidad es mostrar cómo se hace un blog.
También os lo podéis descargar (publicado bajo la GPL): slog-0.1.tar.gz.
Un saludo y eso, muchas gracias
Sin embargo, creo que la filosofía del patrón MVC no consiste en albergar la lógica de negocio en el controlador. El controlador si se encarga del control de flujo entre las vistas, de enviar mensajes al modelo y de controlar temas como la seguridad, rendimiento, etc.
Pienso que sería más escalable tu modelo si toda esa lógica de negocio la gestionases también desde el modelo. Y luego si procede subdividiría el modelo en clases de negocio y clase de acceso a datos.
Si te fijas en el patrón J2EE (MVC por antonomasia) divide los componentes en JSP (vista), Servlets(controlador) y por ultimo EJBs(negocio) y JBean(negocio y acceso a datos)
-------------------
Parse error: parse error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /home/chs/publiactiva.com/home/html/ftp/ecoactiva/lib/mvc.inc.php on line 8
------------------
y he estado mirando... pero como esto de mvc para mi es nuevo, no tengo ni idea de que puede ser, alguien me puede ayudar? les estaria muy agradecido.
Gracias de antemano.Saludos
Yo uso Code Igniter para MVC pero esto está impresionante. Saludos,
if (!file_exists('app/controllers/'.$controller . '/' . $action.'.php'))
Para ver que el controlador exista y la accion tambien.
Aunque se supone que la "accion" es una funcion dentro del controlador no? o del modelo ;)
Me parece muy interesante el tutorial.
Me encuentro un problema: el fichero tgz con el código fuente no existe. El de la segunda parte del tutorial, tampoco. ¿Se han borrado, han cambiado de sitio? Teniendo en cuenta que no reproduces literalmente el código en el artículo, sin el fichero de código estoy algo perdido, aunque siga las explicaciones.
Saludos
Walt
Pues no es muy serio eso de estar dando links para descargas y que te encuentres con esto :
/download/slog-0.1.tar.gz
La página solicitada no existe.
Ponte a trabajar, o haz desaparecer el tutorial.
Prometo rehacerlo de nuevo.
pingback en pixelco.us
code.google.com/p/sloggy/downloads/detail?name=slog-0.1.tar.gz

