Modelo vista controlador (MVC) en PHP [Actualizado 2022]

Modelo vista controlador (MVC) en PHP [Actualizado 2022]

PHP

MVC es un patrón de arquitectura de software, que aporta cierto orden y escalabilidad en los desarrollos. En un primer momento puede resultar intimidante o complejo, pero si lo analizamos con calma, comprobaremos que tal dificultad no es real.

Como programadores deberemos conocer este paradigma y la forma correcta de aplicarlo. A continuación veremos una pequeña explicación y un ejemplo muy simple en PHP.

¿Qué es MVC?

MVC significa modelo (model) vista (view) controlador (controller). Esto es lo que significan cada uno de esos componentes.

Definiciones

Modelo: se encarga de, por ejemplo, cargar datos y realizar operaciones en ellos.
Vista: el frontend o interfaz gráfica de usuario (GUI), es la capa que vé el usuario.
Controlador: se encarga de solicitar datos al modelo y enviarlos a la vista. Interconecta el frontend con el backend.

¿Qué nos permite MVC?

Entre las ventajas que nos aporta el modelo vista controlador podremos destacar: escalabilidad, orden de código y facilmente entendible.

¿Por qué deberías usar MVC?

MVC está ampliamente implantado y aceptado en el desarrollo de software desde hace muchísimos años.

Te ayuda a dividir el código frontend y e backend, permitiendo así realizar cambios en las aplicaciones a varios equipos, por ejemplo programadores PHP y diseñadores de interfaces web.

Ejemplo práctico

Para este ejemplo vamos a programar en PHP una aplicación muy simple que nos permita listar, editar y eliminar notas de texto.

Base de datos

Las notas que creemos se guardarán en una base de datos, que podremos crear con este sql:

CREATE DATABASE mvc_example; 

CREATE TABLE `note` (
  `id` int(11) NOT NULL,
  `title` varchar(75) NOT NULL,
  `content` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `note`
  ADD PRIMARY KEY (`id`);

Estructura de ficheros

  • [re-directory]mvc
    • [re-directory]config
      • [re-file]config.php
    • [re-directory]controller
      • [re-file]note.php
    • [re-directory]model
      • [re-file]db.php
      • [re-file]note.php
    • [re-directory]view
      • [re-directory]template
        • [re-file]footer.php
        • [re-file]header.php
      • [re-file]confirm_delete_note.php
      • [re-file]delete_note.php
      • [re-file]edit_note.php
      • [re-file]list_note.php
    • [re-file]index.php

Este ejemplo es muy sencillo, ya que sólo utilizaremos un controlador y dos modelos. El único controlador se encargará de gestionar las notas, y los dos modelos serán uno para notas y otro para la base de datos.

config/config.php

En este fichero guardaremos información básica que atañe tanto a la conexión con base de datos como o tros parámetros. Por ejemplo, el controlador y la acción por defecto.

Si esta aplicación creciera, seguramente el uso de este fichero estaría más justificado.

<?php

/* Database connection values */
define("DB_HOST", "localhost");
define("DB", "mvc_example");
define("DB_USER", "root");
define("DB_PASS", "");

/* Default options */
define("DEFAULT_CONTROLLER", "note");
define("DEFAULT_ACTION", "list");

?>

controller/note.php

Será el único controlador de nuestra aplicación. Se encarga de recibir las peticiones desde la vista, solicitar información y/u ordenar cambios al modelo.

<?php 

require_once 'model/note.php';

class noteController{
	public $page_title;
	public $view;

	public function __construct() {
		$this->view = 'list_note';
		$this->page_title = '';
		$this->noteObj = new Note();
	}

	/* List all notes */
	public function list(){
		$this->page_title = 'Listado de notas';
		return $this->noteObj->getNotes();
	}

	/* Load note for edit */
	public function edit($id = null){
		$this->page_title = 'Editar nota';
		$this->view = 'edit_note';
		/* Id can from get param or method param */
		if(isset($_GET["id"])) $id = $_GET["id"];
		return $this->noteObj->getNoteById($id);
	}

	/* Create or update note */
	public function save(){
		$this->view = 'edit_note';
		$this->page_title = 'Editar nota';
		$id = $this->noteObj->save($_POST);
		$result = $this->noteObj->getNoteById($id);
		$_GET["response"] = true;
		return $result;
	}

	/* Confirm to delete */
	public function confirmDelete(){
		$this->page_title = 'Eliminar nota';
		$this->view = 'confirm_delete_note';
		return $this->noteObj->getNoteById($_GET["id"]);
	}

	/* Delete */
	public function delete(){
		$this->page_title = 'Listado de notas';
		$this->view = 'delete_note';
		return $this->noteObj->deleteNoteById($_POST["id"]);
	}

}

?>

model/db.php

Creamos este modelo para facilitar las operaciones contra base de datos.

<?php 

require_once 'config/config.php';

class Db {

	private $host;
	private $db;
	private $user;
	private $pass;
	public $conection;

	public function __construct() {		

		$this->host = constant('DB_HOST');
		$this->db = constant('DB');
		$this->user = constant('DB_USER');
		$this->pass = constant('DB_PASS');

		try {
           $this->conection = new PDO('mysql:host='.$this->host.'; dbname='.$this->db, $this->user, $this->pass);
        } catch (PDOException $e) {
            echo $e->getMessage();
            exit();
        }

	}

}

?>

model/note.php

Dentro de nuestro ejemplo, es el modelo principal. Contiene métodos que operan la información en la base de datos.

Por ejemplo, tenemos un método para cargar las notas, y otros para cargar una nota, actualizar etc.

<?php 

class Note {

	private $table = 'note';
	private $conection;

	public function __construct() {
		
	}

	/* Set conection */
	public function getConection(){
		$dbObj = new Db();
		$this->conection = $dbObj->conection;
	}

	/* Get all notes */
	public function getNotes(){
		$this->getConection();
		$sql = "SELECT * FROM ".$this->table;
		$stmt = $this->conection->prepare($sql);
		$stmt->execute();

		return $stmt->fetchAll();
	}

	/* Get note by id */
	public function getNoteById($id){
		if(is_null($id)) return false;
		$this->getConection();
		$sql = "SELECT * FROM ".$this->table. " WHERE id = ?";
		$stmt = $this->conection->prepare($sql);
		$stmt->execute([$id]);

		return $stmt->fetch();
	}

	/* Save note */
	public function save($param){
		$this->getConection();

		/* Set default values */
		$title = $content = "";

		/* Check if exists */
		$exists = false;
		if(isset($param["id"]) and $param["id"] !=''){
			$actualNote = $this->getNoteById($param["id"]);
			if(isset($actualNote["id"])){
				$exists = true;	
				/* Actual values */
				$id = $param["id"];
				$title = $actualNote["title"];
				$content = $actualNote["content"];
			}
		}

		/* Received values */
		if(isset($param["title"])) $title = $param["title"];
		if(isset($param["content"])) $content = $param["content"];

		/* Database operations */
		if($exists){
			$sql = "UPDATE ".$this->table. " SET title=?, content=? WHERE id=?";
			$stmt = $this->conection->prepare($sql);
			$res = $stmt->execute([$title, $content, $id]);
		}else{
			$sql = "INSERT INTO ".$this->table. " (title, content) values(?, ?)";
			$stmt = $this->conection->prepare($sql);
			$stmt->execute([$title, $content]);
			$id = $this->conection->lastInsertId();
		}	

		return $id;	

	}

	/* Delete note by id */
	public function deleteNoteById($id){
		$this->getConection();
		$sql = "DELETE FROM ".$this->table. " WHERE id = ?";
		$stmt = $this->conection->prepare($sql);
		return $stmt->execute([$id]);
	}

}

?>

En el directorio view tendremos las vistas de nuestra aplicación. Como tanto el header como el footer serán los mismos en todas las vistas, en lugar de repetir el código, los crearemos en el directorio template y los llamaremos en las vistas.

El footer simplemente cerrará las etiquetas body y html, además del div que tiene la clase container y que abrimos en el header.

</div>
</body>
</html>

view/template/header.php

En esta parte simplementes abriremos las etiquetas necesarias de html, así como incrustaremos las llamadas a las librerías que vamos a utilizar. En nuestro caso, para no tener que trabajar demasiado los estilos, utilizaremos Bootstrap.

Además, mostraremos en el encabezado un título de página, que se cargará de forma dinámica desde nuestro controlador.

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title></title>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
</head>
<body>
	<div class="container">
		<header class="mb-5">
			<div class="p-5 text-center bg-light" style="margin-top: 58px;">
				<h1 class="mb-3"><?php echo $controller->page_title; ?></h1>
				<h4 class="mb-3">-</h4>
			</div>
		</header>

view/confirm_delete_note.php

Esta vista se mostrará cuando el usuario haga click en eliminar una nota. Simplemente pide confirmación, por si el usuario se arrepiente y no desea eliminar la nota realmente.

Para que sea más claro, mostraremos el título de la nota que se dispone a eliminar.

<div class="row">
	<form class="form" action="index.php?controller=note&action=delete" method="POST">
		<input type="hidden" name="id" value="<?php echo $dataToView["data"]["id"]; ?>" />
		<div class="alert alert-warning">
			<b>¿Confirma que desea eliminar esta nota?:</b>
			<i><?php echo $dataToView["data"]["title"]; ?></i>
		</div>
		<input type="submit" value="Eliminar" class="btn btn-danger"/>
		<a class="btn btn-outline-success" href="index.php?controller=note&action=list">Cancelar</a>
	</form>
</div>

view/delete_note.php

Es la vista que se carga a continuación de haber eliminado una nota. Nos muestra un simple mensaje informativo de que hemos eliminado la nota, y un link para volver al listado.

<div class="row">
	<div class="alert alert-success">
		Nota eliminada correctamente. <a href="index.php?controller=note&action=list">Volver al listado</a>
	</div>
</div>

view/edit_note.php

Esta vista nos mostrará un formulario en el que podremos tanto crear una nota, como edtar una existente.

<?php
$id = $title = $content = "";

if(isset($dataToView["data"]["id"])) $id = $dataToView["data"]["id"];
if(isset($dataToView["data"]["title"])) $title = $dataToView["data"]["title"];
if(isset($dataToView["data"]["content"])) $content = $dataToView["data"]["content"];

?>
<div class="row">
	<?php
	if(isset($_GET["response"]) and $_GET["response"] === true){
		?>
		<div class="alert alert-success">
			Operación realizada correctamente. <a href="index.php?controller=note&action=list">Volver al listado</a>
		</div>
		<?php
	}
	?>
	<form class="form" action="index.php?controller=note&action=save" method="POST">
		<input type="hidden" name="id" value="<?php echo $id; ?>" />
		<div class="form-group">
			<label>Título</label>
			<input class="form-control" type="text" name="title" value="<?php echo $title; ?>" />
		</div>
		<div class="form-group mb-2">
			<label>Contenido</label>
			<textarea class="form-control" style="white-space: pre-wrap;" name="content"><?php echo $content; ?></textarea>
		</div>
		<input type="submit" value="Guardar" class="btn btn-primary"/>
		<a class="btn btn-outline-danger" href="index.php?controller=note&action=list">Cancelar</a>
	</form>
</div>

view/list_note.php

Es la vista que se encarga de mostrarnos las notas ya creadas, si las hay. Además tiene un botón para crear una nota nueva.

<div class="row">
	<div class="col-md-12 text-right">
		<a href="index.php?controller=note&action=edit" class="btn btn-outline-primary">Crear nota</a>
		<hr/>
	</div>
	<?php
	if(count($dataToView["data"])>0){
		foreach($dataToView["data"] as $note){
			?>
			<div class="col-md-3">
				<div class="card-body border border-secondary rounded">
					<h5 class="card-title"><?php echo $note['title']; ?></h5>
					<div class="card-text"><?php echo nl2br($note['content']); ?></div>
					<hr class="mt-1"/>
					<a href="index.php?controller=note&action=edit&id=<?php echo $note['id']; ?>" class="btn btn-primary">Editar</a>
					<a href="index.php?controller=note&action=confirmDelete&id=<?php echo $note['id']; ?>" class="btn btn-danger">Eliminar</a>
				</div>
			</div>
			<?php
		}
	}else{
		?>
		<div class="alert alert-info">
			Actualmente no existen notas.
		</div>
		<?php
	}
	?>
</div>

index.php

En este fichero recibiremos todas las peticiones, tanto para el controlador de notas como para otros si los hubiere.

<?php 

require_once 'config/config.php';
require_once 'model/db.php';

if(!isset($_GET["controller"])) $_GET["controller"] = constant("DEFAULT_CONTROLLER");
if(!isset($_GET["action"])) $_GET["action"] = constant("DEFAULT_ACTION");

$controller_path = 'controller/'.$_GET["controller"].'.php';

/* Check if controller exists */
if(!file_exists($controller_path)) $controller_path = 'controller/'.constant("DEFAULT_CONTROLLER").'.php';

/* Load controller */
require_once $controller_path;
$controllerName = $_GET["controller"].'Controller';
$controller = new $controllerName();

/* Check if method is defined */
$dataToView["data"] = array();
if(method_exists($controller,$_GET["action"])) $dataToView["data"] = $controller->{$_GET["action"]}();


/* Load views */
require_once 'view/template/header.php';
require_once 'view/'.$controller->view.'.php';
require_once 'view/template/footer.php';

?>

La forma de acceder a nuestra aplicación, si por ejemplo la hemos creado en nuestro propio pc, sería algo así:

http://localhost/mvc/index.php?controller=note&action=list

Llamaremos al archivo index.php, pasándole como parámetros el nombre del controlador y la acción que necesitamos.

Será el propio controlador el que decida que vista se va a cargar, según las que tengamos definidas.

Descargar ejemplo

Si tienes curiosidad por probar este código, puedes descargarlo aquí: https://code.adaweb.es/downloads/mvc.zip

Escribe una respuesta