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:

text
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
└── .htaccess

Paso 2: Actualizar el Modelo (Movie.php)

Primero, agregamos el método para actualizar películas en nuestro modelo:

php
<?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
<?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
<?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
<?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
<?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):

php
// 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
<?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)

php
// 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

  1. Navega a la lista de películas

  2. Haz clic en el botón de edición (ícono de lápiz)

  3. Modifica algunos campos

  4. Envía el formulario

  5. 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:

  1. Formulario de edición con validación de datos

  2. Método update en el modelo para guardar cambios

  3. Control de errores y mensajes de retroalimentación

  4. Interfaz de usuario intuitiva

Próximos Pasos

  1. Implementar soft deletes (eliminación lógica)

  2. Añadir historial de cambios

  3. Implementar búsqueda y filtrado avanzado

  4. 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

Entradas más populares de este blog

Axios para Principiantes - Guía Paso a Paso

15-Tutorial: Crear Película en React con useState

Tutorial de React para Principiantes