DEV Community

Ulises A.F.C
Ulises A.F.C

Posted on • Updated on

Desarrollar un proyecto MVC con PHP puro.

Si quieres desarrollar un proyecto web en PHP te presento una manera de como estructurar tu proyecto con buenas practicas y con el patron de diseño MVC.

Empezemos primero con la estructura de carpetas en tu editor de codigo o IDE favorito:

Image description

Se compone de dos carpetas principales: app/ & public/

En la carpeta public/:

Tendremos carpetas donde tendremos archivos estaticos, carpetas como:

  • css/
  • js/
  • img/

entre otras carpetas como por ejemplo puede ser:

  • fonts/

Adicional un archivo llamado index.php en el cual se va cargar lo necesario de la carpeta app/, para mostrar las vistas del proyecto en conjunto con los modelos y controladores, que al fin y al cabo la estructura del proyecto es MVC (modelo, vista, controlador).

En la carpeta app/:

Tendremos la logica de negocio, osea, tendremos el control de las vistas y los modelos en un controlador.
Carpetas como:

  • config/
  • lib/
  • helpers/
  • models/
  • views/
  • controllers/

Y adicional un archivo llamado init.php.

A medida que vayamos empezando iremos agregando archivos en las carpetas para explicar en que consiste cada archivo.

Si pudiste observar en la imagen de arriba, hay archivos llamados .htaccess. Estos archivos nos ayudaran mucho para evitar acceso a carpetas y mapear URL, veremos poco a poco en que consisten, pero antes te dire en donde iran ubicados estos archivos:

El primer .htaccess lo crearemos en la carpeta raiz del proyecto:

Image description

El segundo .htaccess lo crearemos en la carpeta app/:

Image description

Y el tercer .htaccess lo crearemos en la carpeta public/:

Image description

A continuación escribiremos las instrucciones en los archivos .htaccess:

  • Empezamos por el .htaccess de la carpeta app/ con la siguiente instruccion:
Options -Indexes
Enter fullscreen mode Exit fullscreen mode

Con esa instruccion haremos que no se tenga acceso a esa carpeta ya que tendremos la logica del negocio, el funcionamiento de la aplicacion web y no nos interesa que tengan acceso a dichas carpetas de personas (mal intencionadas) en caso de que asi sea.

  • Continuaremos por el .htaccess de la carpeta public/:

Antes de explicar estas instrucciones debemos saber que por defecto el primer archivo que lee el navegador es el index. Ahora bien, sucede lo siguiente: Al nosotros digitar una url con un archivo que no existe por ejemplo localhost/mvc-php/about.php pues el servidor nos lanza un error 500 llamado Internal Server Error ya que el archivo about.php no lo tenemos en nuestro proyecto.

Es por eso que escribimos las siguientes instrucciones:

<IfModule mod_rewrite.c>
Options -Multiviews
RewriteEngine on
RewriteBase /mvc-php/public
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
</IfModule>
Enter fullscreen mode Exit fullscreen mode

Con estas instrucciones hacemos que se eviten las Multiviews es decir que en nuestro proyecto /mvc-php/public si no encuentra un archivo en la carpeta public/ que no existe pues, redireccionar automaticamente al index.php, adicionalmente pasamos un argumento llamado ?url en donde mas adelante veremos como podremos mapear la URL a travez de ese argumento y extraer esos datos con la funcion explode() de php.

Recuerda que nuestra carpeta o proyecto se llama mvc-php por eso hemos escrito ese nombre en nuestro archivo .htaccess. Si tu proyecto o carpeta se llamara diferente debes ponerle el nombre de tu proyecto para evitar problemas a la hora de correr la web.

  • Terminaremos por el .htaccess de la carpeta raiz:

Antes de explicar las instrucciones, sucede una cosa, cuando abrimos nuestro proyecto en la web a travez de un servidor local nos muestra el directorio de nuestro proyecto:

Image description

Para evitar esto, escribiremos las siguientes instrucciones:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]
</IfModule>
Enter fullscreen mode Exit fullscreen mode

Con estas instrucciones hacemos que nos lea de principio a fin (^$) seguido de la carpeta public/ y nos cargue un archivo con cualquier extension (.*). Este archivo se lo pasamos a travez de la URL en el argumento ?url=$1 del archivo .htaccess de la carpeta public/

Hasta acá hemos terminado con las instrucciones de los archivos .htaccess

A continuación empezaremos a trabajar con la URL para el funcionamiento de nuestro proyecto:

  • Dentro de la carpeta app/ iremos a la carpeta lib/ y crearemos el archivo Core.php y escribiremos el siguiente codigo:
class Core
{

  public function __construct()
  {
    $this->getUrl();
  }

  public function getUrl()
  {
    echo $_GET['url'];
  }
}
Enter fullscreen mode Exit fullscreen mode

Estamos obteniendo la url del navegador, ya explicare de donde sacamos la url, continuando, iremos al fichero index.php de la carpeta public/ y escribiremos lo siguiente:

require_once '../app/init.php';
Enter fullscreen mode Exit fullscreen mode

Recordemos que en el archivo index.php de la carpeta public/ cargaremos todo lo necesario de la carpeta app/

Continuando, iremos al archivo init.php de la carpeta app/ y por el momento escribiremos lo siguiente:

echo "<h1>HELLO WORLD!!!</h1>";
Enter fullscreen mode Exit fullscreen mode

Abramos nuestro proyecto en el navegador por medio del servidor local y veremos el mensaje de HELLO WORLD, si es así, vamos por buen camino.

Image description

Esto era prueba para verificar que funcione lo que tratamos de hacer, volvemos al archivo init.php de la carpeta app/ y cargaremos los ficheros de la carpeta lib/ con las siguientes lineas de codigo:

spl_autoload_register(function($lib){
  require_once 'lib/' . $lib . '.php';
});
Enter fullscreen mode Exit fullscreen mode

Con esto hacemos que vayamos cargando automaticamente los ficheros de la carpeta lib/, luego, para mapear la URL lo haremos en el fichero de Core.php, entonces debemos instanciar la clase que tenemos en Core.php en el archivo index.php de la carpeta public/ con la siguiente linea de codigo:

$init = new Core;
Enter fullscreen mode Exit fullscreen mode

Ahora recargaremos en el navegador nuestro proyecto y no nos debe aparecer nada. Si es asi vamos por buen camino:

Image description

Ahora para probar que estamos obteniendo la URL digitaremos algo en nuestra URL por ejemplo:

Image description

Podemos ver que se imprime la URL, explicaré a detalle como es que obtenemos lo que escribimos despues de localhost/mvc-php/. Recordemos que escribimos algunas instrucciones en el archivo .htaccess de la carpeta public/ y en pocas palabras evitabamos las Multiviews, es decir, si buscabamos un archivo que no existe en nuestro proyecto pues que nos redireccione al index.php y a la vez indicabamos que la base de nuestro proyecto seria /mvc-php/public ya que en esa direccion se encuentra pues valga la redundancia el index.php, luego, pasabamos un parametro ?url=$1 donde todo lo que escribamos despues de nuestra base lo obtendriamos a travez de ese parametro. Por eso es que cuando escribimos una direccion localhost/mvc-php/usuarios/listado.php pues nos imprime justamente usuarios/listado.php

  • Continuando, seguiremos de por lleno en el fichero Core.php:

Recordemos que teniamos una clase con un constructor y un método donde obteniamos la URL. En el método getUrl() borraremos lo que teniamos y escribiremos las siguientes lineas de codigo:

public function getUrl()
{
  if(isset($_GET['url']))
  {
    $url = rtrim($_GET['url'], '/');
    $url = filter_var($url, FILTER_SANITIZE_URL);
    $url = explode('/', $url);

    return $url;
  }
}
Enter fullscreen mode Exit fullscreen mode

Evaluamos si existe un valor en url, si es así entonces, obtenemos la url y retiramos espacios en blanco al final de la cadena con la funcion rtrim(), fijate que agregamos un parametro adicional (/), esto es para que nos elimine ese caracter también ya que una URL se separa por ese caracter.

Continuando, filtramos la url con la funcion filter_var() y lo filtramos como FILTER_SANITIZE_URL. Esto es para que lo considere como una URL en sí.

Luego con la funcion explode() guardamos en un array los valores de la URL separados por el caracter /, es decir, va a recortar una cadena de texto cuando encuentre el caracter /. Y con esto estariamos mapeando la URL.

Para probar que funciona, en el constructor de la clase escribiremos lo siguiente:

public function __construct()
{
  print_r($this->getUrl());
}
Enter fullscreen mode Exit fullscreen mode

Ahora veamos en el navegador y escribamos algo en la URL para que nos devuelva los valores en un array:

Image description

Observemos que en el indice [0] se guardo usuarios, en el indice[1] se guardo mostrar-usuarios, y en el indice[2] se guardo pues el numero 2. Ahora sí, ya tenemos mapeada la URL.

Vamos a terminar de escribir las ultimas lineas de codigo en el constructor, pero antes, debemos definir unas variables, y son las siguientes:

protected $controller;
protected $method;
protected $parameters = [];
Enter fullscreen mode Exit fullscreen mode

Tengamos en cuenta que la url nos devuelve un array lo cual lo interpretaremos de la siguiente manera:

indice[0] va ser igual al controlador
indice[1] va ser igual al metodo del controlador
indice[2] va ser igual a los parametros que iran como argumento en los metodos

Luego dentro del constructor escribiremos las siguientes lineas de codigo:

public function __construct()
{
  $url = $this->getUrl();

  if(file_exists('../app/controllers/' . ucwords($url[0]) . '.php'))
  {
    $this->controller = ucwords($url[0]);
    unset($url[0]);
  }

  require_once '../app/controllers/' . $this->controller . '.php';
  $this->controller = new $this->controller;

  if(isset($url[1]))
  {
    if(method_exists($this->controller, $url[1]))
    {
      $this->method = $url[1];
      unset($url[1]);
    }
  }

  $this->parameters = $url ? array_values($url) : [];
  call_user_func_array([$this->controller, $this->method], $this->parameters);
}
Enter fullscreen mode Exit fullscreen mode

Empezemos explicando, primero evaluamos si existe un controlador en la carpeta controllers/ con la funcion file_exists y tambien utilizamos la funcion ucwords() para convertir en mayusculas la primer letra ya que por ende estaremos usando clases y las clases empiezan con una letra mayuscula. Entonces, si el archivo existe procedemos a asignar el valor del indice[0] de la url a la variable $controller, asi mismo utilizamos la funcion unset() para destruir el valor anterior del indice[0]. Luego lo que hacemos es requerir ese archivo y como es una clase hacemos instancia de la misma.

Segundo, evaluamos si existe un valor en el indice[1] osea, un metodo, y este metodo se encuentra dentro del controlador entonces tambien evaluamos si el metodo existe con la funcion method_exists, si el metodo existe pues asignamos el valor del indice[1] de la url a la variable $method, y de igual forma utilizamos la funcion unset() para destruir el valor anterios del indice[1].

Tercero, con los parametros recordemos que le asignamos un array vacio, entonces vamos asignar a $parameters el valor de $url y pues $url es otro array con valores. Si te fijas, usamos operadores ternarios, es decir si $parameters es igual a url entonces utilizamos la funcion array_values() que lo que hace es devolver los valores de un array, con eso evitamos guardar un array dentro de otro array. Y si no hay nada en $parameters pues seguira siendo un array vacio. Para finalizar utilizamos la funcion call_user_func_array() y esta recibe dos parametros, primero, recibe un callback, segundo, el parametro a pasar a la callback es por eso que pasamos como callback un array con los valores de $controller y $method, y pues el parametro que se va a pasar es el de $parameters.

Hasta acá hemos terminado de configurar el Core.php, ahora bien, como saber si esto funciona. Pues vamos a la carpeta controllers/ y crearemos un archivo, yo lo llamare Views.php, recordemos que estas deben ser clases. Dentro escribiremos algunos metodos, esto lo haremos con fin de demostrar, para seguir el ejemplo puedes escribir las siguientes lineas de codigo:

class Views
{

  public function inicio()
  {
    echo "<h1>Pagina de Inicio</h1>";
  }

  public function update($id)
  {
    echo "Update view " . $id;
  }

}
Enter fullscreen mode Exit fullscreen mode

Vayamos al navegador y escribamos la direcion indicando el controlador y el metodo que queremos que se cargue:

Image description

Y en efecto vemos que se imprime lo que hemos indicado en el metodo inicio().

Probemos con el otro metodo:

Image description

Y en efecto vemos que recibe el parametro e imprime lo que hemos indicado en el metodo update($parametro).

A continuacion agregaremos otro fichero a la carpeta lib/ llamado Control.php

Cual será la función de este archivo, pues, se encargará de cargar los modelos y las vistas de sus respectivas carpetas, osea, desde models/ y de views/pages/

Entonces escribiremos las siguientes lineas de codigo:

class Control
{
  public function load_model($model)
  {
    require_once '../app/models/' . $model . '.php';

    return new $model;
  }

  public function load_view($view, $datos = [])
  {
    if(file_exists('../app/views/pages/' . $view . '.php'))
    {
      require_once '../app/views/pages/' . $view . '.php';
    }
    else
    {
      die("404 NOT FOUND");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Explicando, pues el metodo load_model($model) se encarga de requerir el modelo que vamos a utilizar en el controlador. Y el metodo load_view($view, $datos = []) se encarga de requerir la vista que queremos que se cargue en el controlador, adicional podemos mandar un array de datos para mostrarlo en la vista.

Ya una vez teniendo esta clase con los metodos que nos ayudaran a cargar los modelos y las vistas, haremos que la clase View.php herede de Control.php para tener acceso a esos metodos, y eso se hace de la siguiente manera:

class Views extends Control

Ahora, antes de probar esos metodos debemos de terminar de configurar nuestro proyecto.

  • En la carpeta config/ crearemos un fichero llamado config.php, y escribiremos lo siguiente:
define('APP', dirname(dirname(__FILE__)));
define('URL', 'http://localhost/mvc-php');
Enter fullscreen mode Exit fullscreen mode

En pocas palabras estamos definiendo dos constantes y como valor estamos asignando una ruta, en el caso de la constante APP la ruta es hacia las carpetas dentro de APP, y en el caso de URL estamos definiendo la url del proyecto, esto nos ayudara a tener acceso de archivos de ambas carpetas.

  • Para que este archivo nos funcione, lo vamos a requerir en init.php y vamos a escribir lo siguiente:

require_once 'config/config.php';

  • Luego nos iremos a la carpeta de views/ y a la carpeta de inc/ donde crearemos dos archivos llamados header.php y footer.php.

Primero editaremos el fichero de header.php y escribiremos lo siguiente:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="<?= URL . '/css/bootstrap.min.css' ?>">
  <title><?= $datos['title'] ?></title>
</head>
<body>
Enter fullscreen mode Exit fullscreen mode

Simplemente es codigo HTML5 con la diferencia que estamos cargando estilos CSS3, en este caso pues de Bootstrap. Ademas, fijate que en title tenemos codigo PHP, esto es porque cuando vayamos a cargar una vista desde el controlador le podemos pasar parametros en un array, y cuando le demos un valor a title se imprimira automaticamente ahí.

Ahora en footer.php escribiremos lo siguiente:

  <script src="<?= URL . '/js/bootstrap.bundle.min.js' ?>"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Y pues, es codigo HTML5, estamos cargando el archivo JavaScript de Bootstrap, y a la vez estamos cerrando las etiquetas de body y html.

Estos archivos son repetitivos, por lo tanto con esto nos ahorramos escribir en cada vista este codigo.

  • Ahora vayamos a la carpeta de views/ y a la carpeta de pages/ y crearemos un fichero llamado inicio.php y escribiremos lo siguiente:
<?php require_once APP . '/views/inc/header.php' ?>

<div class="container-fluid bg-light py-5">
  <div class="container">
    <h1 class="display-4">Welcome!!!</h1>
  </div>
</div>

<?php require_once APP . '/views/inc/footer.php' ?>
Enter fullscreen mode Exit fullscreen mode

Fijate que estamos cargando header.php al inicio, y tambien estamos cargando footer.php al final. Con esto, cada vez que vayamos a escribir codigo HTML5 ya tendremos el codigo reutlizable y solo lo tendremos que requerir con require(). Esto nos ayudara a optimizar un poco el codigo en estos archivos.

También estamos mostrando pues un mensaje Welcome con algunas clases de bootstrap para darle estilos a nuestras paginas.

  • Ya teniendo esto, hoy si vayamos a Views.php dentro de controllers/ y editaremos el metodo llamado inicio():
public function inicio()
{
  $datos = [
    "title" => "Inicio"
  ];
  $this->load_view('inicio', $datos);
}
Enter fullscreen mode Exit fullscreen mode

Fijate que en el metodo inicio(), primero, asignamos un array llamado $datos y creamos un key llamado title, el valor que este obtenga se mostrara en la etiqueta de cada pagina. Luego hacemos uso del metodo que nos ayuda a cargar las views/pages que queremos mostrar, solamente indicamos el nombre de la vista, y le pasamos el array de $datos para que tambien muestre el titulo de la pagina.

Finalmente, probemos como es que va terminar funcionando:

  • Vayamos al navegador y nos dirigimos a la URL de nuestro proyecto:

Image description

No nos aparecera nada, pero si en la URL indicamos el controlador y el metodo que queremos ver, nos muestra lo siguiente:

Image description

Y vemos que se carga la pagina de inicio que habiamos creado en pages/views/inicio.php por medio del controlador y el metodo dentro del cual requerimos esa vista con el metodo load_view().

CONCLUSIONES:

Hasta aquí hemos terminado de cierta manera el funcionamiento del proyecto, el cual consiste en aplicar el patron de diseño MVC (modelo, vista, controlador) y a la vez aplicar POO (programacion orientada a objetos). También decir que, consideraría este proyecto como un template donde puedes usarlo para crear un proyecto para desarrollar un CRUD.

Hasta acá dejaré desarrollado este proyecto, y posteriormente subiré otra nota donde usaremos este mismo proyecto pero realizando un CRUD y como poder desarrollarlo.

Te dejo el link del repositorio en GITHUB y puedas usar este template para desarrollar un proyecto.

Si puedes deja una estrella al repositorio :p

template-mvc-php

Top comments (1)

Collapse
 
mferrazuy profile image
Mauricio Ferraz

Muy buen artículo! Felicitaciones, espero que puedas compartir más artículos como este.