1. Code
  2. PHP

Creando un motor de blog de archivos planos usando Slim

En este artículo, aprenderemos cómo crear un motor de blog de archivos planos en PHP utilizando el framework Slim. Si no comprendes la jerga, no te preocupes. Simplemente vamos a crear una aplicación espartana para un blog, que utiliza archivos de texto en lugar de una base de datos para almacenar los datos.
Scroll to top
12 min read

Spanish (Español) translation by Steven (you can also view the original English article)

En este artículo, aprenderemos cómo crear un motor de blog de archivos planos en PHP utilizando el framework Slim. Si no comprendes la jerga, no te preocupes. Simplemente vamos a crear una aplicación espartana para un blog, que utiliza archivos de texto en lugar de una base de datos para almacenar los datos.

Si eres un principiante, ¡no te preocupes! Comenzaremos con los conceptos básicos del uso de Slim. ¡Empecemos después del salto!


Introducción

Slim es un framework PHP RESTful ligero para crear sitios web simples. Está inspirado en Sinatra, un framework escrito en Ruby. Slim se envía con algunos componentes mínimos, como Request, Response y View, que son los únicos componentes necesarios en nuestro motor de blog de archivos planos.


Paso 1: Introducción

Para comenzar, configuremos una aplicación de "hola mundo". Antes de eso, debes descargar el framework Slim en tu sistema. No vamos a discutir mucho sobre Slim ya que ya se ha cubierto aquí en Tuts+. Idealmente, deberías tener los siguientes archivos en tu carpeta:

  • Slim/ - Framework Slim
  • Index.php - El archivo index
  • .htaccess - Para la reescritura de la URL

Ahora abre index.php, donde verás un montón de HTML dentro de la siguiente sección. Lo eliminé todo y lo reemplacé con "Hello world". Es de esperar que index.php se vea así ahora.

1
require 'Slim/Slim.php';
2
$app = new Slim();
3
$app->get('/', function () {
4
    echo "<h1>Hello world</h1>";
5
});
6
$app->run();

Creé un Virtualhost en mi máquina, que es la URL que se muestra en la captura de pantalla. Ajusta la URL según la ubicación de Slim en tu máquina.

Navega hasta la URL y verás una página de inicio con el texto "Hello world".

get() es un método de Slim, que tomará una ruta como primer argumento y una función de devolución de llamada correspondiente como el último argumento. Aparte de get, también podemos tener rutas para los verbos POST, PUT y DELETE. Como Slim es compatible con PHP 5.3, la función de devolución de llamada puede escribirse, y se escribe, como una función anónima.


Paso 2: Renderizar una página PHP

El siguiente paso es renderizar un archivo PHP. Antes de continuar, crea un directorio llamado templates para almacenar todos nuestros archivos de plantilla. Cualquier HTML estático o archivos de plantilla que creemos se colocarán en esta carpeta. Slim nos permite especificar la ruta a nuestros archivos de plantilla en su configuración. Podemos agregar una configuración como se muestra a continuación.

1
$app->config(array(
2
   'templates.path' => './templates'
3
));

Creemos una página de información para nuestro blog. Crea un archivo PHP llamado about.php y colócalo en la carpeta templates. Agrega el siguiente código:

1
<html>
2
<head>
3
	<title>A Slim Blog engine</title>
4
</head>
5
<body>
6
	<h1>About page</h1>
7
	<p>
8
		This page is an example of static route, rendering a php file.
9
	</p>
10
</body>
11
</html>

Para que Slim procese una solicitud, necesitamos definir una ruta correspondiente que se pueda asignar a esa URL mediante Route. En nuestro primer paso, hemos agregado una ruta al index o '/'. Ahora agreguemos otra ruta para nuestra página "Acerca de".

1
$app->get('/about', function () use ($app) {
2
    $app->render('about.php');
3
});

Si cargas http://slim.local/about en tu navegador, con suerte debería mostrar algo como esto:

Hemos agregado una ruta a la página "Acerca de", que mostrará about.php ubicado en ./templates (¿recuerdas la variable de configuración template.path?). ¿Notaste que usamos use ($app) dentro del método get? Bueno, esta es solo una forma de usar una variable dentro de una función anónima que está fuera de su alcance.


Paso 3: Asignar valores a la plantilla

Ahora podemos mostrar una página de plantilla para una ruta. Es hora de pensar en mostrar algunos valores dinámicos en la plantilla, ya que es posible que no estemos renderizando y mostrando archivos estáticos todo el tiempo. Podemos asignar valores para ser renderizados en una plantilla desde el método render(). Debe pasarse como segundo parámetro como una matriz asociativa. Cambiemos el código anterior para que se vea así:

1
$app->get('/about', function () use ($app) {
2
    $data = array(
3
	    'heading' => 'About page',
4
	    'message' => 'This page is an example of static route, rendering a php file.'
5
	);
6
    $app->render('about.php',$data);
7
});

Y cambia un poco el "body" de la plantilla.

1
<body>
2
	<h1><?php echo $heading; ?></h1>
3
	<p>
4
		<?php echo $message; ?>
5
	</p>
6
</body>

Notarás que puedes ver la misma página si vuelves a cargar la URL anterior. Las variables utilizadas en el archivo de plantilla son las claves correspondientes en la matriz asociativa.


Paso 4: Añadir una ruta dinámica

Hasta ahora, hemos estado jugando con algunas rutas estáticas, '/' y '/about'. Ahora vamos a crear una ruta dinámica, es decir, una ruta que puede responder a diferentes URL.

1
$app->get('/:param1/:param2', function ($param1,$param2) use ($app) {
2
    echo $param1 .' - ' . $param2;
3
});

Slim siempre invoca la primera ruta que coincide con la solicitud HTTP actual. Esto significa que todas las rutas estáticas deben definirse antes que las rutas dinámicas.

Si cargas http://slim.local/first-param/second-param en tu navegador, se mostrará first-param - second-param.

La ruta de una variable debe comenzar con un ':'. Slim pasará el valor de esta variable como argumento a nuestra función de devolución de llamada, para que podamos analizarla y realizar una operación adecuada. Slim espera exactamente dos parámetros para la ruta anterior, ya que las dos variables de ruta son obligatorias. Si no está presente, Slim mostrará un error 404. Podemos hacer que un parámetro de URL sea opcional como se muestra en el siguiente paso.


Paso 5: Parámetros de ruta opcionales

Para que un parámetro de ruta sea opcional, vuelve a escribir el código anterior como se muestra a continuación:

1
$app->get('/:param1(/:param2(/:param3))', function () use ($app) {
2
	$args = func_get_args();
3
	foreach($args as $arg){
4
		echo $arg . ' -- ';	
5
	}
6
});

Con eso, el segundo y tercer parámetro son opcionales. Podemos usar el método func_get_args() para obtener todos los argumentos que se pasan a la función de devolución de llamada.

Muy bien, es hora de abordar un asunto serio. Ahora tenemos toda la información necesaria para crear un motor de blogs de archivos planos. Juntemos el conocimiento discutido anteriormente para crearlo.


Paso 6: Crear un sistema de archivos

Necesitamos crear un sistema de archivos antes de juntar todos estos componentes diferentes. Aquí hay un sistema de archivos simple para nuestra aplicación.

Este es un sistema de archivos minimalista con solo los archivos/carpetas requeridos. Todos los artículos se guardarán en la carpeta articles. La carpeta assets albergará nuestros archivos CSS y JavaScript, así como imágenes. Slim contendrá los archivos del framework y los archivos de la plantilla.


Paso 7: Agregar una ruta para la vista del artículo

Si te sientes un poco perdido, aquí tienes una vista general rápida de cómo está estructurada nuestra aplicación.

  • El artículo se escribirá en un archivo de texto con la URL como el nombre del archivo.
  • Nuestra aplicación asignará las URL a los artículos correspondientes.
  • Para nuestra conveniencia, mantendremos la metainformación (como el nombre del autor, la fecha, etc.) de un artículo dentro del archivo de texto en JSON. Nos ayudará a recuperarlos usando la función json_decode() incorporada de PHP. El contenido y los metadatos se separarán con una línea en blanco.

Agreguemos una ruta que cargará un artículo desde la carpeta de artículos según la URL.

1
// add article location in configuration

2
$app->config(array(
3
   'templates.path' => 	'./templates',
4
   'article.path'	=>	'./articles'   // location of articles

5
));
6
// '/post-url' will load post-url.txt file.

7
$app->get('/:article',function($article) use($app){
8
	$path 	 = $app->config('article.path');
9
	//open text file and read it

10
	$handle  = fopen($path . '/' . $article . '.txt', 'r');
11
	$content = stream_get_contents($handle);
12
	// split the content to get metadata

13
	$content = explode("\n\n", $content);
14
	$rawMeta = array_shift($content);
15
	// metadata is json encoded. so decode it.

16
	$meta    = json_decode($rawMeta,true);
17
	$content = implode("\n\n", $content);
18
	$article = array('meta' => $meta , 'content' => $content);
19
	$app->render('article.php', $article);
20
});

Hemos agregado una ruta dinámica con un solo parámetro de ruta. La función de devolución de llamada recibirá el valor de ese parámetro, que debería ser un nombre de archivo sin su extensión. A continuación, extraemos el valor de la variable de configuración article.path, donde guardamos nuestros artículos.

En las siguientes dos líneas, leemos ese archivo y almacenamos su contenido en una variable, $content. Como mencioné en el párrafo anterior, un artículo tendrá metadatos y contenido real que estarán separados por una sola línea ("\n\n"). A menudo, puede haber muchas otras líneas en blanco en el contenido de un artículo, lo que probablemente romperá el método actual. Para evitar eso, usaremos el primer elemento para obtener metadatos y uniremos el resto de la matriz usando la misma línea en blanco. Dado que los metadatos están en formato JSON, necesitamos decodificarlos desde aquí y almacenarlos en la matriz $meta.

Crea un archivo de plantilla para representar un artículo y colócalo en la carpeta template.

1
//article.php

2
echo '<h2>' . $meta['title'] . '</h2>';
3
echo $content;

Creemos nuestra primera publicación de blog ahora. Crea un nuevo archivo llamado first-article.txt, colócalo en la carpeta articles y agrega el contenido como se muestra a continuación. Asegúrate de tener los metadatos y el contenido separados por una línea en blanco.

1
//first-article.txt

2
{
3
	"title" : "This is my first article",
4
	"date"  : "02/15/2012",
5
	"slug"  : "first-article",
6
	"author": "Author name"
7
}
8
9
Fruitcake jelly-o halvah marshmallow bonbon. Croissant candy canes chocolate cake muffin jelly beans liquorice.... (copy paste some lipsum)

¡Excelente! Puedes comenzar a publicar artículos ahora. ¡Pero espera! Todavía no tenemos una página de listado. Necesitamos enumerar todos los artículos disponibles en nuestro sistema, con su título y una pequeña descripción. Para hacer esto, necesitaremos analizar la carpeta de artículos para encontrar todos los artículos y agregarlos a una matriz leyéndolos uno tras otro.


Paso 8: Crear una página para el listado

1
$app->get('/', function() use ($app){
2
	$path = $app->config('article.path');
3
	$dir = new DirectoryIterator($path);
4
	$articles = array();
5
	foreach($dir as $file){
6
		if($file->isFile()){
7
			$handle  = fopen($path . '/' . $file->getFilename(), 'r');
8
			$content = stream_get_contents($handle);
9
			$content = explode("\n\n", $content);
10
			$rawMeta = array_shift($content);
11
			$meta    = json_decode($rawMeta,true);
12
			$content = implode("\n\n", $content);
13
			$articles[$file->getFilename()] = array('meta' => $meta, 'content' => $content);
14
		}
15
	}
16
	$app->render('index.php',array('articles' => $articles));
17
});

Aquí, hemos agregado una ruta a la página de inicio. Estamos usando la clase Directoryiterator incorporada de PHP para recorrer cada archivo en el directorio. Cada artículo se agrega a la matriz $articles. Desde el archivo de plantilla (index.php), podemos recorrer estos artículos como se muestra a continuación.

1
foreach($articles as $article){
2
	echo "<h1> ". $article['meta']['title'] ." </h1> ";
3
	echo substr(strip_tags($article['content']), 0,200) 
4
			. '... <a href="/' . $article['meta']['slug'] 
5
			. '">Read more >> </a>';
6
}

Nuestra página de inicio está lista ahora. Enumerará todos los artículos de nuestro blog, con su título correspondiente y una parte del contenido.


Paso 9: Crear una página de archivos

A continuación, crearemos una página de 'archives'. Como esperamos que la página de archivos tenga filtros basados en año, mes y fecha, agregaremos una ruta con parámetros opcionales. La página de archivos admitirá las siguientes URL.

  • /archives
  • /archives/yyyy
  • /archives/yyyy/mm
  • /archives/yyyy/mm/dd

Para lograr esto, cargaremos todos los artículos y los filtraremos en función de los argumentos pasados a la función de devolución de llamada. Moví todo a una clase para que podamos comenzar a reutilizar nuestro código. Una ruta que admita las URL anteriores se verá así:

1
	// assign $this to another variable as it is not supported inside closure

2
	$blog = new Blog();
3
	$slim->get('/archives(/:yyyy(/:mm(/:dd)))', function() use ($blog,$slim) {});

Ten en cuenta que en esta ruta, el año, mes y fecha son parámetros opcionales. /archives es la única parte necesaria de la URL. A continuación, debemos implementar esta ruta que responderá en función de los parámetros opcionales.

1
	$args  = func_get_args();
2
	//load all articles

3
	$articles = $blog->loadArticles();
4
	$archives = array();
5
	// check count($args) for optional route params

6
	if(count($args)>0) {
7
		switch(count($args)){
8
			case 1 :    //only year is present

9
				$format = 'Y';
10
				$date = $dateFormat($args,$format);
11
				break;
12
			case 2 :    //year and month are present

13
				$format = 'Y-m';
14
				$date = $dateFormat($args,$format);
15
				break;
16
			case 3 :	//year, month and date are present

17
				$format = 'Y-m-d';
18
				$date = $dateFormat($args,$format);
19
				break;
20
		}
21
		// filter articles

22
		foreach($articles as $article){
23
			if($dateFormat($article['meta']['date'], $format) == $date){
24
				$archives[] = $article;
25
			}
26
		}
27
	}
28
	else{
29
		$archives = $articles;
30
	}
31
	// render archives

32
	$slim->render('archives.php',array('archives' => $archives));

Dentro de la declaración switch, creamos la fecha que se filtrará utilizando los argumentos pasados. Esta fecha se compara con la fecha de cada artículo y, si coinciden, se agrega a la matriz $archives. $dateFormat() es una función anónima dentro de la ruta para formatear fechas.

1
	$dateFormat = function($args,$format){
2
		$temp_date = is_array($args) ? implode('-', $args) : $args;
3
		$date   = new DateTime($temp_date);
4
		return $date->format($format);
5
	};

Podemos escribir condiciones para una ruta que deben ser satisfechas por los argumentos, para validar el año, mes y fecha pasados a la ruta.

1
$slim->get('/archives(/:yyyy(/:mm(/:dd)))', function() use $blog {
2
})->conditions(
3
		array(
4
			 'yyyy' => '(19|20)\d\d'
5
			,'mm'=>'(0[1-9]|1[0-2])'
6
			,'dd'=>'(0[1-9]|[1-2][0-9]|3[0-1])'
7
		));

Aquí, el año debe comenzar con 19 o 20 seguido de dos dígitos. El mes debe estar entre 1 y 12 y la fecha debe estar entre 01 y 31.

Aquí está la página de archivos que he creado con el código anterior. Si notaste que he usado Bootstrap de Twitter para aplicar algunos estilos básicos, ¡obtendrás una galleta adicional!


Paso 9: Condimentarlo

Bueno, ahora poseemos un modelo funcional de un motor de blog con archivos planos. Lo siguiente que debemos hacer es organizar la aplicación para evitar la duplicación de código y agregar funciones como comentarios, etc. Movamos este código de index.php a una clase separada para una mejor organización.

Hasta ahora, hemos utilizado la clase View del framework Slim. Podemos crear una clase de vista personalizada que ampliará Slim_View para agregar algunas características adicionales como establecer un diseño base, configuraciones globales, etc. Si prefieres escribir artículos en Markdown, también puedes incluir un analizador de Markdown.

También debemos buscar mejorar la estética de la aplicación. Prefiero usar Bootstrap de Twitter ya que es realmente muy fácil de usar y personalizar. No creo que sea una buena idea profundizar en estos detalles aquí. Los he compilado en una aplicación simple llamada TextPress que se puede descargar aquí.


Terminando

Casi todos los motores de blog de archivos planos prefieren vivir en la nube. Además, lo más probable es que Git se utilice para publicar artículos. Con nuestro motor, puedes crear publicaciones usando un archivo de texto simple y publicarlo usando la línea de comandos. Como no hay un panel de administración para piratear, es mucho más seguro que casi cualquier otro sistema de gestión de contenido. Sobre todo, es fácil de alojar, ya que los servicios de plataforma, como PHP Fog, nos permiten alojar aplicaciones libremente en la nube.

Así que eso es todo. Déjame saber si tienes alguna pregunta en los comentarios a continuación y ¡muchas gracias por leer!