19-Actualizar Película
En este tutorial aprenderemos a implementar la funcionalidad para actualizar películas en nuestro sistema de gestión de películas. Esta es una operación CRUD (Create, Read, Update, Delete) esencial.
Requisitos Previos
Proyecto de gestión de películas en funcionamiento
Funcionalidades de listar y crear películas ya implementadas
Conexión a base de datos configurada
Paso 1: Estructura del Proyecto
Antes de comenzar, revisemos la estructura de nuestro proyecto:
proyecto-peliculas/
│
├── src/
│ ├── controllers/
│ │ ├── MovieController.php
│ │ └── ...
│ ├── models/
│ │ ├── Movie.php
│ │ └── ...
│ ├── views/
│ │ ├── movies/
│ │ │ ├── index.php
│ │ │ ├── create.php
│ │ │ ├── edit.php
│ │ │ └── show.php
│ │ └── ...
│ └── config/
│ └── database.php
│
├── public/
│ └── index.php
└── .htaccessPaso 2: Actualizar el Modelo (Movie.php)
Primero, agregamos el método para actualizar películas en nuestro modelo:
<?php
// src/models/Movie.php
class Movie {
// ... (propiedades y otros métodos existentes)
/**
* Actualiza una película existente
*
* @param int $id ID de la película a actualizar
* @param array $data Datos actualizados
* @return bool True si se actualizó correctamente
*/
public function update($id, $data) {
$conn = Database::getConnection();
$sql = "UPDATE movies SET
title = :title,
director = :director,
release_year = :release_year,
genre = :genre,
duration = :duration,
synopsis = :synopsis,
rating = :rating,
updated_at = NOW()
WHERE id = :id";
$stmt = $conn->prepare($sql);
return $stmt->execute([
':title' => $data['title'],
':director' => $data['director'],
':release_year' => $data['release_year'],
':genre' => $data['genre'],
':duration' => $data['duration'],
':synopsis' => $data['synopsis'],
':rating' => $data['rating'],
':id' => $id
]);
}
/**
* Obtiene una película por su ID
*
* @param int $id ID de la película
* @return array|null Datos de la película o null si no existe
*/
public function find($id) {
$conn = Database::getConnection();
$sql = "SELECT * FROM movies WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->execute([':id' => $id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
?>Paso 3: Actualizar el Controlador (MovieController.php)
Ahora implementamos los métodos en el controlador:
<?php
// src/controllers/MovieController.php
class MovieController {
// ... (métodos existentes)
/**
* Muestra el formulario para editar una película
*
* @param int $id ID de la película a editar
*/
public function edit($id) {
// Verificar si el usuario está autenticado (si aplica)
// Auth::check();
$movieModel = new Movie();
$movie = $movieModel->find($id);
if (!$movie) {
Session::flash('error', 'Película no encontrada');
header('Location: /movies');
exit();
}
// Mostrar vista de edición
require_once '../src/views/movies/edit.php';
}
/**
* Procesa la actualización de una película
*/
public function update($id) {
// Verificar si el usuario está autenticado (si aplica)
// Auth::check();
// Verificar que sea una petición POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /movies');
exit();
}
// Validar datos del formulario
$errors = $this->validateMovieData($_POST);
if (!empty($errors)) {
// Si hay errores, volver al formulario con los datos
$movie = $_POST;
$movie['id'] = $id;
require_once '../src/views/movies/edit.php';
return;
}
// Procesar la actualización
$movieModel = new Movie();
// Preparar datos
$data = [
'title' => trim($_POST['title']),
'director' => trim($_POST['director']),
'release_year' => (int)$_POST['release_year'],
'genre' => trim($_POST['genre']),
'duration' => (int)$_POST['duration'],
'synopsis' => trim($_POST['synopsis']),
'rating' => (float)$_POST['rating']
];
// Intentar actualizar
if ($movieModel->update($id, $data)) {
Session::flash('success', 'Película actualizada correctamente');
header('Location: /movies/' . $id);
} else {
Session::flash('error', 'Error al actualizar la película');
header('Location: /movies/' . $id . '/edit');
}
exit();
}
/**
* Valida los datos de la película
*
* @param array $data Datos a validar
* @return array Errores de validación
*/
private function validateMovieData($data) {
$errors = [];
// Validar título
if (empty(trim($data['title']))) {
$errors['title'] = 'El título es requerido';
} elseif (strlen(trim($data['title'])) > 255) {
$errors['title'] = 'El título no puede exceder los 255 caracteres';
}
// Validar director
if (empty(trim($data['director']))) {
$errors['director'] = 'El director es requerido';
}
// Validar año de lanzamiento
$currentYear = date('Y');
if (empty($data['release_year']) ||
!is_numeric($data['release_year']) ||
$data['release_year'] < 1888 || // Año de la primera película
$data['release_year'] > $currentYear + 5) { // Permitir películas futuras
$errors['release_year'] = 'El año debe ser válido';
}
// Validar género
if (empty(trim($data['genre']))) {
$errors['genre'] = 'El género es requerido';
}
// Validar duración
if (empty($data['duration']) ||
!is_numeric($data['duration']) ||
$data['duration'] <= 0 ||
$data['duration'] > 600) {
$errors['duration'] = 'La duración debe ser un número positivo (minutos)';
}
// Validar rating
if (!empty($data['rating']) &&
(!is_numeric($data['rating']) ||
$data['rating'] < 0 ||
$data['rating'] > 10)) {
$errors['rating'] = 'El rating debe ser un número entre 0 y 10';
}
return $errors;
}
}
?>Paso 4: Crear la Vista de Edición (edit.php)
Creamos el formulario de edición:
<?php
// src/views/movies/edit.php
$title = 'Editar Película';
ob_start();
?>
<div class="container mt-4">
<h1 class="mb-4">Editar Película</h1>
<?php if (Session::has('error')): ?>
<div class="alert alert-danger">
<?= Session::flash('error') ?>
</div>
<?php endif; ?>
<form action="/movies/<?= $movie['id'] ?>/update" method="POST">
<!-- Campo oculto para método PUT (si no soportas PUT en formularios HTML) -->
<input type="hidden" name="_method" value="PUT">
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<!-- Título -->
<div class="mb-3">
<label for="title" class="form-label">Título *</label>
<input type="text"
class="form-control <?= isset($errors['title']) ? 'is-invalid' : '' ?>"
id="title"
name="title"
value="<?= htmlspecialchars($movie['title'] ?? '') ?>"
required>
<?php if (isset($errors['title'])): ?>
<div class="invalid-feedback">
<?= $errors['title'] ?>
</div>
<?php endif; ?>
</div>
<!-- Director -->
<div class="mb-3">
<label for="director" class="form-label">Director *</label>
<input type="text"
class="form-control <?= isset($errors['director']) ? 'is-invalid' : '' ?>"
id="director"
name="director"
value="<?= htmlspecialchars($movie['director'] ?? '') ?>"
required>
<?php if (isset($errors['director'])): ?>
<div class="invalid-feedback">
<?= $errors['director'] ?>
</div>
<?php endif; ?>
</div>
<!-- Sinopsis -->
<div class="mb-3">
<label for="synopsis" class="form-label">Sinopsis</label>
<textarea class="form-control <?= isset($errors['synopsis']) ? 'is-invalid' : '' ?>"
id="synopsis"
name="synopsis"
rows="4"><?= htmlspecialchars($movie['synopsis'] ?? '') ?></textarea>
<?php if (isset($errors['synopsis'])): ?>
<div class="invalid-feedback">
<?= $errors['synopsis'] ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<!-- Año de lanzamiento -->
<div class="mb-3">
<label for="release_year" class="form-label">Año de lanzamiento *</label>
<input type="number"
class="form-control <?= isset($errors['release_year']) ? 'is-invalid' : '' ?>"
id="release_year"
name="release_year"
value="<?= $movie['release_year'] ?? '' ?>"
min="1888"
max="<?= date('Y') + 5 ?>"
required>
<?php if (isset($errors['release_year'])): ?>
<div class="invalid-feedback">
<?= $errors['release_year'] ?>
</div>
<?php endif; ?>
</div>
<!-- Género -->
<div class="mb-3">
<label for="genre" class="form-label">Género *</label>
<select class="form-control <?= isset($errors['genre']) ? 'is-invalid' : '' ?>"
id="genre"
name="genre"
required>
<option value="">Seleccionar género</option>
<option value="Acción" <?= ($movie['genre'] ?? '') == 'Acción' ? 'selected' : '' ?>>Acción</option>
<option value="Comedia" <?= ($movie['genre'] ?? '') == 'Comedia' ? 'selected' : '' ?>>Comedia</option>
<option value="Drama" <?= ($movie['genre'] ?? '') == 'Drama' ? 'selected' : '' ?>>Drama</option>
<option value="Ciencia Ficción" <?= ($movie['genre'] ?? '') == 'Ciencia Ficción' ? 'selected' : '' ?>>Ciencia Ficción</option>
<option value="Terror" <?= ($movie['genre'] ?? '') == 'Terror' ? 'selected' : '' ?>>Terror</option>
<option value="Romance" <?= ($movie['genre'] ?? '') == 'Romance' ? 'selected' : '' ?>>Romance</option>
<option value="Animación" <?= ($movie['genre'] ?? '') == 'Animación' ? 'selected' : '' ?>>Animación</option>
<option value="Documental" <?= ($movie['genre'] ?? '') == 'Documental' ? 'selected' : '' ?>>Documental</option>
</select>
<?php if (isset($errors['genre'])): ?>
<div class="invalid-feedback">
<?= $errors['genre'] ?>
</div>
<?php endif; ?>
</div>
<!-- Duración -->
<div class="mb-3">
<label for="duration" class="form-label">Duración (minutos) *</label>
<input type="number"
class="form-control <?= isset($errors['duration']) ? 'is-invalid' : '' ?>"
id="duration"
name="duration"
value="<?= $movie['duration'] ?? '' ?>"
min="1"
max="600"
required>
<?php if (isset($errors['duration'])): ?>
<div class="invalid-feedback">
<?= $errors['duration'] ?>
</div>
<?php endif; ?>
</div>
<!-- Rating -->
<div class="mb-3">
<label for="rating" class="form-label">Rating (0-10)</label>
<input type="number"
class="form-control <?= isset($errors['rating']) ? 'is-invalid' : '' ?>"
id="rating"
name="rating"
value="<?= $movie['rating'] ?? '' ?>"
step="0.1"
min="0"
max="10">
<?php if (isset($errors['rating'])): ?>
<div class="invalid-feedback">
<?= $errors['rating'] ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Botones de acción -->
<div class="mt-3">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Cambios
</button>
<a href="/movies/<?= $movie['id'] ?>" class="btn btn-secondary">
<i class="fas fa-times"></i> Cancelar
</a>
<a href="/movies" class="btn btn-outline-secondary">
<i class="fas fa-list"></i> Volver al listado
</a>
</div>
</div>
</div>
</form>
</div>
<?php
$content = ob_get_clean();
include '../src/views/layout.php';
?>Paso 5: Actualizar el Enrutador
Añadimos las nuevas rutas a nuestro enrutador:
<?php
// public/index.php o tu archivo de enrutamiento principal
// ... (rutas existentes)
// Rutas para películas
$router->get('/movies', 'MovieController@index');
$router->get('/movies/create', 'MovieController@create');
$router->post('/movies/store', 'MovieController@store');
$router->get('/movies/(\d+)', 'MovieController@show');
$router->get('/movies/(\d+)/edit', 'MovieController@edit'); // Nueva ruta
$router->post('/movies/(\d+)/update', 'MovieController@update'); // Nueva ruta
$router->post('/movies/(\d+)/delete', 'MovieController@delete');
// ... (más rutas)
?>Paso 6: Agregar Enlaces de Edición en la Vista de Listado
Actualizamos la vista index.php para incluir enlaces de edición:
<?php
// src/views/movies/index.php (fragmento)
?>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Título</th>
<th>Director</th>
<th>Año</th>
<th>Género</th>
<th>Rating</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($movies as $movie): ?>
<tr>
<td><?= $movie['id'] ?></td>
<td>
<a href="/movies/<?= $movie['id'] ?>">
<?= htmlspecialchars($movie['title']) ?>
</a>
</td>
<td><?= htmlspecialchars($movie['director']) ?></td>
<td><?= $movie['release_year'] ?></td>
<td>
<span class="badge bg-primary">
<?= htmlspecialchars($movie['genre']) ?>
</span>
</td>
<td>
<?php if ($movie['rating']): ?>
<span class="badge bg-warning text-dark">
<?= number_format($movie['rating'], 1) ?>/10
</span>
<?php else: ?>
<span class="badge bg-secondary">Sin rating</span>
<?php endif; ?>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="/movies/<?= $movie['id'] ?>"
class="btn btn-outline-info"
title="Ver">
<i class="fas fa-eye"></i>
</a>
<a href="/movies/<?= $movie['id'] ?>/edit"
class="btn btn-outline-primary"
title="Editar">
<i class="fas fa-edit"></i>
</a>
<form action="/movies/<?= $movie['id'] ?>/delete"
method="POST"
class="d-inline"
onsubmit="return confirm('¿Estás seguro de eliminar esta película?')">
<button type="submit"
class="btn btn-outline-danger"
title="Eliminar">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>Paso 7: Mejoras Opcionales
7.1 Manejo de Métodos HTTP (PUT/PATCH)
Si quieres usar métodos HTTP semánticos (PUT para actualizar):
// En tu enrutador
$router->put('/movies/(\d+)', 'MovieController@update');
// En el formulario de edición
<form action="/movies/<?= $movie['id'] ?>" method="POST">
<input type="hidden" name="_method" value="PUT">
<!-- resto del formulario -->
</form>7.2 Clase de Validación
Puedes crear una clase separada para validación:
<?php
// src/validators/MovieValidator.php
class MovieValidator {
public static function validate($data, $isUpdate = false) {
$errors = [];
// Reglas de validación
return $errors;
}
}
?>7.3 Manejo de Archivos (Imágenes de Portada)
// En el método update del controlador
if (isset($_FILES['poster']) && $_FILES['poster']['error'] === UPLOAD_ERR_OK) {
$uploadDir = 'uploads/posters/';
$fileName = uniqid() . '_' . basename($_FILES['poster']['name']);
$uploadFile = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['poster']['tmp_name'], $uploadFile)) {
$data['poster_path'] = $uploadFile;
// Eliminar poster anterior si existe
if (!empty($movie['poster_path']) && file_exists($movie['poster_path'])) {
unlink($movie['poster_path']);
}
}
}Paso 8: Pruebas
Navega a la lista de películas
Haz clic en el botón de edición (ícono de lápiz)
Modifica algunos campos
Envía el formulario
Verifica que los cambios se hayan guardado
Posibles Errores y Soluciones
Error 1: "Película no encontrada"
Verifica que el ID exista en la base de datos
Revisa la ruta del enlace de edición
Error 2: "No se pudieron actualizar los datos"
Verifica los permisos de escritura en la base de datos
Revisa que todos los campos requeridos estén presentes
Error 3: "Método no permitido"
Asegúrate de que el formulario use POST
Verifica que la ruta esté correctamente definida en el enrutador
Conclusión
Has implementado exitosamente la funcionalidad de actualizar películas en tu sistema. Esta funcionalidad incluye:
Formulario de edición con validación de datos
Método update en el modelo para guardar cambios
Control de errores y mensajes de retroalimentación
Interfaz de usuario intuitiva
Próximos Pasos
Implementar soft deletes (eliminación lógica)
Añadir historial de cambios
Implementar búsqueda y filtrado avanzado
Añadir paginación para listados largos
¡Felicidades! Has completado una parte fundamental del CRUD de tu aplicación de gestión de películas
Comentarios
Publicar un comentario