desarrollo en entorno servidor

eL FRAMEWORK DE SYMFONY 2

eugeniaperez.es

eugeniaperez.es

instalación y configuración

eugeniaperez.es

creación de un proyecto symfony

  • composer create-project symfony/framework-standard-edition project_name

Vamos a dejarlo

en htdocs.

eugeniaperez.es

creación de un proyecto symfony

  • Durante el proceso de creación del proyecto Symfony pide parámetros de configuración para la conexión a base de datos, servidor de correo, etc. De momento lo dejamos vacío
  • El proyecto se crea con un archivo composer.json en la raíz
    • ​Contiene todas las dependencias del proyecto
    • Se almacenan en el directorio vendor (ocupan bastante espacio en disco)
    • Por tanto, no es necesario copiarlas para compartir un proyecto, ya que se pueden regenerar gracias a composer

eugeniaperez.es

creación de un proyecto symfony

  • Comandos de composer:
    • composer install: descarga las dependencias de un proyecto a partir del archivo composer.json para crear el vendor
    • composer update: busca actualizaciones y nuevas versiones en las librerías de un proyecto y las descarga

eugeniaperez.es

creación de un proyecto symfony

  • Por tanto, cuando trabajamos con un repositorio de código fuente, es habitual excluir el directorio lib de las operaciones de commit
  • Por ejemplo, en git:
/web/bundles/
/app/bootstrap.php.cache
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
/app/phpunit.xml
/build/
/vendor/
/bin/
/composer.phar
/nbproject/private/

Ejemplo de .gitignore

eugeniaperez.es

creación de un proyecto symfony

Crea el proyecto que acabamos de generar con

Composer en el Netbeans:

New Project -> PHP Application with existing sources

 

eugeniaperez.es

ESTRUCTURA de un proyecto symfony

  • Todo el código reside en un bundle (por defecto AppBundle)
  • app/Resources: almacena las vistas
  • app/config: contiene toda la configuración 
  • app/logs: guarda los ficheros de log
  • src/AppBundle: almacena los controladores y rutas de Symfony, código del modelo de dominio (por ejemplo Doctrine) y la lógica de negocio general de la aplicación
  • vendor: es el directorio donde Composer instala las dependencias de la aplicación
  • web: almacena todos los recursos estáticos de la aplicación, incluyendo imágenes, hojas de estilo, JavaScript, etc.

eugeniaperez.es

PROYECTO DE EJEMPLO

  • Descargar de https://github.com/symfony/symfony-demo
  • Descomprimir
  • Abrir ventana de comandos sobre la raíz del proyecto y ejecutar
    • composer install (poner locale a 'es')
  • Abrir con Netbeans (new project with existing sources)
  • Copiar a htdocs y abrir el navegador apuntando a la raíz del proyecto
    • http://localhost:8888/symfony-demo-master/web/app_dev.php/

eugeniaperez.es

PROYECTO DE EJEMPLO

eugeniaperez.es

RUTAS

  • Definen qué acción de qué controlador va a responder a una petición del cliente
  • 2 maneras de definirlas:
    • app/config/rounting.yml
    • Anotaciones a las acciones

eugeniaperez.es

RUTAS

  • Una petición de tipo GET a la ruta /controlador/{age}, será tratada por el controlador CalculatorController del bundle AppBundle, y dentro de este se invocará a la acción indexAction pasándole como parámetro la edad 
//routing.yml
app:
    path: /calculator/{age}
    defaults: {_controller: AppBundle:Calculator:index}


//CalculatorController.php
class CalculatorController extends Controller {

    public function indexAction($age) {
        $currentYear = date('Y');

        return new Response('<html><body>Current year: ' . $currentYear . 
		'<br/>Year of birth: ' . ($currentYear - $age) . '</body></html>');
    }

}

eugeniaperez.es

RUTAS

  • Las rutas se definen de manera habitual mediante anotaciones en los propios controladores
  • Por ejemplo, accede a la raíz de la parte pública del blog
  • http://localhost:8888/symfony-demo-master/web/app_dev.php/es/blog/
  • Analicemos el código de la acción que maneja esta petición

eugeniaperez.es

RUTAS

  • La primera anotación de la función indexAction de la clase BlogController se puede explicar de la siguiente manera:
    • "/": indica que maneja peticiones a la URL raíz del sitio
    • name: "blog_index": le damos nombre a la ruta para poder utilizarla en otras partes de la aplicación
    • defaults={"page" = 1}: si en la URL no aparece el parámetro page, se le asigna por defecto el valor 1
/**
 * @Route("/", name="blog_index", defaults={"page" = 1})
 * @Route("/page/{page}", name="blog_index_paginated", requirements={"page" : "\d+"})
 */
public function indexAction($page)
{
    ...
}

eugeniaperez.es

RUTAS

  • Crea un proyecto vacío books
    • composer create-project symfony/framework-standard-edition books
  • Crea un proyecto nuevo en Netbeans con la opción with existing sources a partir del proyecto books
  • Comprueba que el archivo app/config/routing.yml tiene el siguiente contenido:

 

 

  • Con esa anotación indicamos que vamos a definir las rutas mediante anotaciones en los controladores
app:
    resource: "@AppBundle/Controller/"
    type:     annotation

eugeniaperez.es

RUTAS

  • Abre el archivo src/AppBundle/Controller/DefaultController.php
  • Renómbralo a BookController.php
  • Comprueba que ya viene con una acción indexAction() creada
  • Comprueba que esta acción contiene una ruta a la URL raíz del sitio y se llame homepage
  • De momento, esta acción simplemente va a hacer una redirección a una vista
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request) {
        return $this->render('book/index.html.twig');
    }

Quita la línea con base_dir de la vista...

eugeniaperez.es

CONTROLADORES

  • Un controlador, en el patrón MVC, tiene como rol comunicar la vista con el modelo

  • Recibirá las peticiones fruto de la interacción del usuario con la vista, ejecutará la lógica necesaria para resolver la petición, y enviará el modelo de nuevo a la vista con la información que se necesite presentar de vuelta al usuario.

  • En Symfony un controlador es una clase PHP que contendrá acciones, cada una de las cuáles podrá ser llamada mediante una URL única.

eugeniaperez.es

CONTROLADORES

  • Un controlador suele recibir el nombre del objeto sobre el que actúa más la palabra Controller. Por ejemplo, BookController o UserController

  • Cada controlador contiene acciones que permiten operar sobre esa entidad. Estas acciones pueden ser accedidas por los clientes mediante peticiones REST

  • Por ejemplo, en el proyecto demo, el BlogController tiene las siguientes acciones:

    • indexAction

    • postShowAction

    • commentNewAction

    • commentFormAction

eugeniaperez.es

CONTROLADORES

  • Como se puede ver, las acciones suelen recibir un nombre que consta de la acción que realizan más la palabra action. 

  • Accede a la URL: http://localhost:8888/books/web/app_dev.php

    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request) {
        return $this->render('book/index.html.twig');
    }

eugeniaperez.es

CONTROLADORES

  • Por el momento esa acción lo único que debe hacer es redirigir al usuario a una vista
  • Para ello utiliza la función render() para enviar la petición a una vista ubicada en app/Resources/views/book/index.html.twig

eugeniaperez.es

VISTAS

  • En MVC, las vistas son los archivos que presentan la información al usuario
  • Las plantillas en Symfony utilizan el motor de plantillas Twig
  • Por lo tanto, el lenguaje en el que estarán escritas serán una combinación de Twig y HTML
  • Las plantillas se guardan en el directorio app/Resources/views
  • En este directorio hay un archivo base.html.twig
    • Es un archivo que define el layout de la página
    • De él pueden heredar otras vistas para reutilizar código

eugeniaperez.es

VISTAS

  • Abre el archivo base.html.twig. Presta atención a los elementos block. Permiten inyectar código en las plantillas que utilizan la plantilla maestra
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

eugeniaperez.es

VISTAS

  • Abre el archivo app/Resources/views/book/index.html.twig.
  • Incluye el código siguiente
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Hello world!</h1>
{% endblock %}

eugeniaperez.es

VISTAS

  • La instrucción extends nos permite indicar que una vista va a utilizar otra como layout
  • Mediante las instrucciones block podemos insertar código en el lugar en que se define dicho bloque en el layout
  • Lanza la URL: http://localhost:8888/books/web/app_dev.php
  • Aparece la siguiente página en pantalla

eugeniaperez.es

VISTAS

  • Inspecciona el código fuente de la página desde el navegador (Ctrl+U)
  • Comprueba que el código generado es una combinación de los dos ficheros vistos (ignora la etiqueta div que aparece antes del cierre del body, ya que es la barra de depuración de Symfony)

eugeniaperez.es

VISTAS

  • Sustituye el contenido de la vista index.html.twig por lo siguiente:
{% extends 'base.html.twig' %}

{% block body %}
<nav class="navbar navbar-inverse" role="navigation">        
</nav>

<!-- Page Content -->
<div class="container">
    <div class="row">
        <div class="col-lg-12">
            <h1>Listado de libros técnicos de programación</h1>

            <table class="table books-table">
                <thead>
                <th>Portada</th>
                <th>Título</th>
                <th>Autor</th>
                <th>Precio</th>
                <th>Acciones</th>
                </thead>
                <tbody>

                </tbody>
            </table>
        </div>
    </div>
</div>
<!-- /.container -->

{% endblock %}

eugeniaperez.es

VISTAS

  • Ahora añadiremos un poco de estilo a la página mediante Bootstrap. Edita el archivo base.html.twig:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}
        <link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
        {% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

eugeniaperez.es

VISTAS

  • Incluimos la CSS de Bootstrap. Debemos también añadir el archivo bootstrap.min.css en el directorio web/css
  • Puedes descargar la CSS de Bootstrap a través de la siguiente URL:
    • http://getbootstrap.com/
  • Obtenemos una página con una tabla para mostrar libros

eugeniaperez.es

VISTAS

  • Como hemos visto, Symfony contempla el concepto de páginas que actúan como layout (por ejemplo base.html.twig) y páginas de contenido que utilizan las anteriores como plantilla
  • Las páginas layout están pensadas para contener código que va a ser repetido en varias páginas. Así evitamos tener que incluirlo en varias páginas
  • Desplazamos la barra superior y el div contenedor al layout

eugeniaperez.es

VISTAS

//base.html.twig

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}
        <link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
        {% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>

        <nav class="navbar navbar-inverse" role="navigation">        
        </nav>

        <!-- Page Content -->
        <div class="container">
          {% block body %}
          {% endblock %}
        </div>

        {% block javascripts %}{% endblock %}
    </body>
</html>

eugeniaperez.es

VISTAS

//index.html.twig

{% extends 'base.html.twig' %}

{% block body %}
<div class="row">
    <div class="col-lg-12">
        <h1>Listado de libros técnicos de programación</h1>

        <table class="table books-table">
            <thead>
            <th>Portada</th>
            <th>Título</th>
            <th>Autor</th>
            <th>Precio</th>
            <th>Acciones</th>
            </thead>
            <tbody>

            </tbody>
        </table>
    </div>
</div>
{% endblock %}

eugeniaperez.es

MODELO

  • El modelo representa los datos que viajan entre los controladores y las vistas
  • Por el momento nuestra aplicación de ejemplo carece de modelos
  • ¿Algún ejemplo?
    • http://localhost:8888/symfony-demo-master/web/app_dev.php/es/blog/

eugeniaperez.es

MODELO

  • Esta página presenta un listado de posts de un blog
  • Los posts son los modelos, ya que es la información que viaja entre el controlador y la vista
  • Cada modelo está representado por una clase con atributos y métodos básicos (principalmente getters/setters)
  • En este ejemplo, por la clase Post almacenada en src/AppBundle/Entity/Post.php

eugeniaperez.es

MODELO

  • Desde el controlador se obtienen los modelos (en este caso procedentes de base de datos, y se pasan a la vista)
public function indexAction($page)
{
    $query = $this->getDoctrine()->getRepository('AppBundle:Post')->queryLatest();

    $paginator = $this->get('knp_paginator');
    $posts = $paginator->paginate($query, $page, Post::NUM_ITEMS);
    $posts->setUsedRoute('blog_index_paginated');

    return $this->render('blog/index.html.twig', array('posts' => $posts));
}

eugeniaperez.es

MODELO

  • Desde la vista se muestran (app/Resources/views/blog/index.html.twig)
{% for post in posts %}
        <article class="post">
            <h2>
                <a href="{{ path('blog_post', { slug: post.slug }) }}">
                    {{ post.title }}
                </a>
            </h2>

            {{ post.summary|md2html }}
        </article>
{% else %}
        <div class="well">{{ 'post.no_posts_found'|trans }}</div>
{% endfor %}

eugeniaperez.es

EJERCICIO

  • En nuestro proyecto de ejemplo, crea una carpeta Entity en src/AppBundle
  • Crea una clase Book.php en dicho directorio

eugeniaperez.es

ejercicio

<?php

namespace AppBundle\Entity;

/**
 * Represents a book.
 *
 * @author Eugenia Pérez <info@eugeniaperez.es>
 * 
 */
class Book {

    private $id;
    private $coverUrl;
    private $title;
    private $author;
    private $price;

    function __construct($id, $coverUrl, $title, $author, $price) {
        $this->id = $id;
        $this->coverUrl = $coverUrl;
        $this->title = $title;
        $this->author = $author;
        $this->price = $price;
    }

 //getters y setters
}

eugeniaperez.es

EJERCICIO

  • Crea varios objetos book en la indexAction de BookController y pásaselos a la vista:
/**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request) {
        $books = array();

        $book1 = new Book(1, 'http://ecx.images-amazon.com/images/I/51Swm3TW72L._SX381_BO1,204,203,200_.jpg', 
                'Don\'t make me think', 'Steve Krug', 30);
        $book2 = new Book(2, 'http://www.infoq.com/resource/minibooks/scrum-xp-from-the-trenches-2/en/cover/Thumb.jpg', 
                'Scrum and XP from the Trenches', 'Henrik Kniberg', 0);
        $book3 = new Book(3, 'http://ecx.images-amazon.com/images/I/410Yirblc6L._SX387_BO1,204,203,200_.jpg', 
                'AngularJS: Novice to Ninja', 'Sandeep Panda', 33);
        $book4 = new Book(4, 'http://ecx.images-amazon.com/images/I/419ewXXyAJL._SX311_BO1,204,203,200_.jpg', 
                'Step by step Bootstrap 3', 'Riwanto Megosinarso', 16);

        array_push($books, $book1);
        array_push($books, $book2);
        array_push($books, $book3);
        array_push($books, $book4);

        return $this->render('book/index.html.twig', array('books' => $books));
    }

eugeniaperez.es

EJERCICIO

  • Para hacer uso de la clase Book tenemos que hacer unas importaciones de código. Aquí se utiliza use:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\Book;

eugeniaperez.es

EJERCICIO

  • Desde la vista books/index.html.twig, itera por el array de books e imprímelos en una tabla:
    {% for book in books %}
                <tr>
                    <td><img class="img-rounded" src="{{ book.coverUrl }}"></td>
                    <td>{{ book.title }}</td>
                    <td>{{ book.author }}</td>
                    <td>{{ book.price }}€</td>
                    <td></td>
                </tr>
    {% else %}
                <tr td colspan="5">No se han encontrado libros</td></tr>
    {% endfor %}
    

eugeniaperez.es

EJERCICIO

eugeniaperez.es

EJERCICIO

Para que no salgan tan grandes podemos definir un estilo en una CSS: main.css

 

table.books-table img{
    max-height: 10em;
    max-width: 6em;
}
<link rel="stylesheet" href="{{ asset('css/main.css') }}">

Incluimos la referencia a la CSS en el fichero base.html.twig

eugeniaperez.es

DOCTRINE ORM

  • La edición standard de Symfony incluye Doctrine, que es un ORM (Object Relational Mapping) para facilitar el acceso a bases de datos relaciones desde aplicaciones que sigan el paradigma de la orientación a objetos
  • Doctrine está totalmente desacoplado de Symfony, por lo que su uso es opcional
  • Además, no sólo es capaz de conectarse a bases de datos relacionales, sino que a través de la librería Doctrine ODM también puede hacer lo propio con sistemas gestores de bases de datos NoSQL

eugeniaperez.es

DOCTRINE ORM

  • Lo primero que debemos hacer es configurar los parámetros de conexión a la base de datos
  • Estos se encuentran en el archivo parameters.yml situado en app/config
parameters:
    database_host: 127.0.0.1
    database_port: null
    database_name: books
    database_user: root
    database_password: root

eugeniaperez.es

DOCTRINE ORM

  • Creamos la base de datos. Para ello ejecutamos el siguiente comando ubicados en el directorio raíz del proyecto:
    • php app/console doctrine:database:create

eugeniaperez.es

DOCTRINE ORM

  • Añadimos las anotaciones necesarias a la clase Book recién creada para que sea persistida
    • Importación del namespace de Doctrine
    • @Entity para anotar una entidad que va a ser persistida
    • El tipo de cada columna.
    • El tamaño para aquellos que son de tipo string.
    • El ID o clave primaria de la tabla.

DOCTRINE ORM

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Represents a book.
 *
 * @author Eugenia Pérez <info@eugeniaperez.es>
 * 
 *  * @ORM\Entity
 */
class Book {

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")     
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $coverUrl;
    
    /**
     * @ORM\Column(type="string", length=100)
     */
    private $title;
    
    /**
     * @ORM\Column(type="string", length=100)
     */
    private $author;
    
    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    private $price;

...
?>

Como el id es AUTO se quita del constructor

eugeniaperez.es

DOCTRINE ORM

  • Ejecutamos el siguiente comando para crear la tabla correspondiente en la base de datos:
    • php app/console doctrine:schema:update --force

eugeniaperez.es

DOCTRINE ORM

  • Inserta varios libros manualmente en la base de datos. Puedes utilizar MySQL Workbench

 

INSERT INTO `books`.`book` (`cover_url`, `title`, `author`, `price`)
VALUES
("http://ecx.images-amazon.com/images/I/51Swm3TW72L._SX381_BO1,204,203,200_.jpg",
"Don't make me think", "Steve Krug", 30)

 

eugeniaperez.es

DOCTRINE ORM

  • INSERT INTO `books`.`book` (`cover_url`, `title`, `author`, `price`)
    VALUES("http://www.infoq.com/resource/minibooks/scrum-xp-from-the-trenches-2/en/cover/Thumb.jpg","Scrum and XP from the Trenches", "Henrik Kniberg", 0)
  • INSERT INTO `books`.`book` (`cover_url`, `title`, `author`, `price`)
    VALUES("http://ecx.images-amazon.com/images/I/410Yirblc6L._SX387_BO1,204,203,200_.jpg","AngularJS: Novice to Ninja", "Sandeep Panda", 33)

 

eugeniaperez.es

DOCTRINE ORM

  • INSERT INTO `books`.`book` (`cover_url`, `title`, `author`, `price`)
    VALUES("http://ecx.images-amazon.com/images/I/419ewXXyAJL._SX311_BO1,204,203,200_.jpg","Step by step Bootstrap 3", "Riwanto Megosinarso", 16)

 

eugeniaperez.es

DOCTRINE ORM

  • Para obtener datos de la base de datos Doctrine utiliza el concepto de repositorio
  • Un repositorio es una clase generada por Doctrine que se asocia a una clase de nuestro modelo de dominio y contiene métodos de acceso a datos

eugeniaperez.es

DOCTRINE ORM

  • Obtenemos el repositorio a partir de la entidad que queremos recuperar de base de datos
    public function indexAction(Request $request) {        
        $books = $this->getDoctrine()->getRepository('AppBundle:Book')
                ->findAll();

        return $this->render('book/index.html.twig', array('books' => $books));
    }

Convenio para nombrar la entidad

eugeniaperez.es

DOCTRINE ORM

  • Carga de nuevo el listado en el navegador y comprueba que puedes visualizar la tabla de libros correctamente, pero esta vez con los libros que han sido recuperados de base de datos

eugeniaperez.es

DOCTRINE ORM

  • Como hemos visto, el repositorio también nos permite hacer búsquedas con filtros por atributos
  • Creamos un formulario de búsqueda en la barra superior de la página
  • Edita el archivo base.html.twig. Añade el siguiente código al elemento <nav>

eugeniaperez.es

DOCTRINE ORM

        //base.html.twig
        <nav class="navbar navbar-inverse" role="navigation">        
            <form class="navbar-form navbar-right" role="search"                
                  action="{{ path('search_books') }}">>
                <div class="input-group">
                    <input type="text" class="form-control" placeholder="Buscar..." 
                        name="searchTerm" id="srch-term">
                    <div class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <i class="glyphicon glyphicon-search"></i>
                        </button>
                    </div>
                </div>
            </form>
        </nav>

eugeniaperez.es

DOCTRINE ORM

  • El HTML definido también utiliza Bootstrap
  • El botón de búsqueda lleva una imagen de una lupa que para que se muestre es necesario cargar el paquete de iconos de esta librería
  • En el paquete que te descargaste de Bootstrap, busca el directorio fonts y pégalo debajo del directorio web del proyecto

eugeniaperez.es

DOCTRINE ORM

  • Para que el formulario de búsqueda no quede tan pegado a la derecha podemos añadir la siguiente regla a la main.css:
  •  
form.navbar-right{
    margin-right: 5em;
}

eugeniaperez.es

DOCTRINE ORM

  • El action del formulario lleva a una acción de un controlador identificada con un nombre
  • El nombre debe coincidir con el de la ruta definida mediante anotaciones o YML
  • Mediante la instrucción {{ path('search_books') }} imprimiremos en el HTML la ruta a la acción que reciba el nombre search_books

eugeniaperez.es

DOCTRINE ORM

  • Añade la siguiente acción a la clase BookController
  • Captura el objeto request y obtiene el parámetro searchTerm enviado desde el formulario de la vista
  • Si es vacío realiza una búsqueda sin criterios (findAll)
  • Si se indica una cadena de búsqueda, busca un título de libro que coincida

eugeniaperez.es

DOCTRINE ORM

    /**
     * Busca libros por nombre.
     * 
     * @Route("/book/search", name="search_books")
     */
    public function searchAction(Request $request) {
        $books = array();
        $searchTerm = $request->get('searchTerm');
        $em = $this->getDoctrine();

        //Si la cadena de búsqueda no está definida o es vacía
        if (!isset($searchTerm) || trim($searchTerm) === '') {
            $books = $em->getRepository('AppBundle:Book')
                    ->findAll();
        } else {
            //Buscar libros cuyo título coincida exactamente con la cadena de búsqueda
            $books = $em->getRepository('AppBundle:Book')
                    ->findBy(array('title' => $searchTerm));                    
        }

        return $this->render('book/index.html.twig', array('books' => $books));
    }

eugeniaperez.es

DOCTRINE ORM

  • Mejora en la búsqueda:
    • Se deben mostrar todos los libros cuyo título contenga la cadena de texto introducida por el usuario
  • Es necesario crear un QueryBuilder

eugeniaperez.es

DOCTRINE ORM

    /**
     * Busca libros por nombre.
     * 
     * @Route("/book/search", name="search_books")
     */
    public function searchAction(Request $request) {
        $books = array();
        $searchTerm = $request->get('searchTerm');
        $em = $this->getDoctrine();

        //Si la cadena de búsqueda no está definida o es vacía
        if (!isset($searchTerm) || trim($searchTerm) === '') {
            $books = $em->getRepository('AppBundle:Book')
                    ->findAll();
        } else {
            //Buscar libros cuyo título contenga la cadena de búsqueda
            $books = $em->getRepository("AppBundle:Book")->createQueryBuilder('b')
                            ->where('b.title LIKE :searchTerm')
                            ->setParameter("searchTerm", '%' . $searchTerm . '%')
                            ->getQuery()->getResult();
        }

        return $this->render('book/index.html.twig', array('books' => $books));
    }

eugeniaperez.es

DOCTRINE ORM

  • Inserción de un nuevo libro
  • Requiere dos acciones:
    • Una para mostrar el formulario de inserción
    • Otra para recibir los datos enviados por el usuario y guardarlos
  • Comenzamos creando un nuevo botón en la barra superior

eugeniaperez.es

DOCTRINE ORM

        //base.html.twig

        <nav class="navbar navbar-inverse" role="navigation"> 
            <div class="navbar-header">                
                <a class="navbar-brand" href="{{ path('homepage') }}">Librería 4V</a>
            </div>
            <ul class="nav navbar-nav">
                <li class="active"><a href="{{ path('create_book') }}">Añadir un libro</a></li>
            </ul>
            <form class="navbar-form navbar-right" role="search" 
                  action="{{ path('search_books') }}">
                <div class="input-group">
                    <input type="text" class="form-control" placeholder="Buscar..." 
                        name="searchTerm" id="srch-term">
                    <div class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <i class="glyphicon glyphicon-search"></i></button>
                    </div>
                </div>
            </form>
        </nav>

eugeniaperez.es

DOCTRINE ORM

  • Al pulsar en el botón de añadir libro, el flujo de ejecución será dirigido a una acción con el nombre create_book
  • Añadimos la siguiente acción a BookController
  • Esta acción nos redirige a la vista create.html.twig
    /**
     * Muestra el formulario de creación de libros.
     * 
     * @Route("/book/create", name="create_book")
     */
    public function createAction() {
        return $this->render('book/create.html.twig');
    }

eugeniaperez.es

DOCTRINE ORM

  • Crea una nueva vista create.html.twig en app/Resources/views/book
  • Añadimos un formulario con campos para los atributos de los libros
  • El formulario será de tipo POST
  • En el action añadiremos la ruta a una acción de BookController que recogerá la información y la insertará en la base de datos

eugeniaperez.es

DOCTRINE ORM

{% extends 'base.html.twig' %}

{% block body %}
<form class="form-horizontal" method="post" action="{{ path('new_book') }}">
    <div class="form-group">
        <label for="title" class="col-sm-2 control-label">Título</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" name="title" id="title" placeholder="Titulo">
        </div>
    </div>
    <div class="form-group">
        <label for="cover" class="col-sm-2 control-label">Portada</label>
        <div class="col-sm-10">
            <input type="url" class="form-control" id="cover" name="cover" placeholder="URL de la portada">
        </div>
    </div>
    <div class="form-group">
        <label for="author" class="col-sm-2 control-label">Autor</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" id="author" name="author" placeholder="Autor">
        </div>
    </div>
    <div class="form-group">
        <label for="price" class="col-sm-2 control-label">Precio</label>
        <div class="col-sm-10">
            <input type="number" class="form-control" id="price" name="price" placeholder="Precio">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-success pull-right">Crear</button>
        </div>
    </div>
</form>

{% endblock %}

eugeniaperez.es

DOCTRINE ORM

  • El formulario anterior envía los datos a la acción de nombre new_book
  • La acción obtiene los datos de la request
  • Guarda los datos mediante las funciones persist() y flush()
  • Una vez guardado el libro, llama a la acción indexAction() para volver al listado

eugeniaperez.es

DOCTRINE ORM

    /**
     * Recibe el nuevo libro a crear y lo persiste en la base de datos.
     * 
     * @Route("/book/new", name="new_book")
     */
    public function newAction(Request $request) {
        $postData = $request->request;

        $book = new Book($postData->get("cover"), $postData->get("title"), 
            $postData->get("author"), $postData->get("price"));
        $em = $this->getDoctrine()->getManager();
        $em->persist($book);
        $em->flush();

        return $this->forward('AppBundle:Book:index');
    }

eugeniaperez.es

DOCTRINE ORM

    /**
     * Recibe el nuevo libro a crear y lo persiste en la base de datos.
     * 
     * @Route("/book/new", name="new_book")
     */
    public function newAction(Request $request) {
        ...

        return $this->forward('AppBundle:Book:index');
    }

Estamos llamando a forward porque queremos redireccionar a una acción: indexAction. Para aprovechar todo código anterior de carga de información de la BD.

eugeniaperez.es

DOCTRINE ORM

  • Implementamos la funcionalidad de borrado de libros
  • Primero añadimos dos botones para editar y eliminar libros en la tabla de resultados
<td class="actions">
    <a href="" class="btn btn-sm btn-default">
        <span class="glyphicon glyphicon-edit"></span> Editar
    </a> 
    <a href="{{ path('delete_book', {'id': book.id}) }}" class="btn btn-sm btn-danger">
        <span class="glyphicon glyphicon-trash"></span> Eliminar
    </a>                         
</td>

eugeniaperez.es

DOCTRINE ORM

eugeniaperez.es

DOCTRINE ORM

  • Al pulsar el botón de borrado se llama a la acción de nombre delete_book pasándole el ID del libro a borrar
  • Creamos una nueva acción deleteAction() en la clase BookController

eugeniaperez.es

DOCTRINE ORM

    /**
     * Borra un libro de la base de datos.
     * 
     * @Route("/book/delete/{id}", name="delete_book")
     */
    public function deleteAction($id) {
        $book = $this->getDoctrine()->getRepository('AppBundle:Book')
                ->find($id);

        if ($book) {
            $em = $this->getDoctrine()->getManager();
            $em->remove($book);
            $em->flush();
            return $this->forward('AppBundle:Book:index');
        }
    }

eugeniaperez.es

DOCTRINE ORM

  • Modificar un libro
    • El procedimiento es similar a la inserción
    • Hacen falta dos acciones:
      • Una para mostrar el formulario
      • Otra para recibir los datos y persistir las modificaciones

eugeniaperez.es

DOCTRINE ORM

  • En BookController creamos una acción para mostrar el formulario de edición con los atributos del libro
    /**
     * Muestra el formulario con el detalle del libro para su edición.
     * 
     * @Route("/book/edit/{id}", name="edit_book")
     */
    public function editAction($id) {
        $book = $this->getDoctrine()->getRepository('AppBundle:Book')
                ->find($id);

        if ($book) {
            return $this->render('book/edit.html.twig', array('book' => $book));
        } else {
            return $this->forward('AppBundle:Book:index');
        }
    }

Añade también el path desde la vista

                                                                                                 <a href="{{ path('edit_book', {'id': book.id}) }}"

eugeniaperez.es

DOCTRINE ORM

  • Creamos una nueva vista edit.html.twig para la edición
  • Es similar a la de creación de libros, pero hay dos variaciones principales:
    • Añadimos un campo oculto para el ID
    • Añadimos el atributo value a cada campo de texto para mostrar la información del libro

eugeniaperez.es

DOCTRINE ORM

{% extends 'base.html.twig' %}

{% block body %}
<form class="form-horizontal" method="post" action="{{ path('update_book') }}">
    <input type="hidden" name="id" value="{{ book.id }}" />
    <div class="form-group">
        <label for="title" class="col-sm-2 control-label">Título</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" name="title" id="title" placeholder="Titulo" value="{{ book.title }}">
        </div>
    </div>
    <div class="form-group">
        <label for="cover" class="col-sm-2 control-label">Portada</label>
        <div class="col-sm-10">
            <input type="url" class="form-control" id="cover" name="cover" placeholder="URL de la portada" value="{{ book.coverUrl }}">
        </div>
    </div>
    <div class="form-group">
        <label for="author" class="col-sm-2 control-label">Autor</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" id="author" name="author" placeholder="Autor" value="{{ book.author }}">
        </div>
    </div>
    <div class="form-group">
        <label for="price" class="col-sm-2 control-label">Precio</label>
        <div class="col-sm-10">
            <input type="number" class="form-control" id="price" name="price" placeholder="Precio" value="{{ book.price }}">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-success pull-right">Modificar</button>
        </div>
    </div>
</form>

{% endblock %}

eugeniaperez.es

DOCTRINE ORM

  • El action del formulario apunta a una acción de nombre update_book
  • Creamos una nueva acción con ese nombre de ruta en BookController
  • Esta acción ejecuta los siguientes pasos:
    • Recupera el objeto de la base de datos a partir de su ID
    • Actualiza todos sus atributos mediante los setters
    • Utiliza el método merge() de Doctrine para hacer la actualización

eugeniaperez.es

DOCTRINE ORM

    /**
     * Recibe el libro con sus campos modificados para su actualización.
     * 
     * @Route("/book/update", name="update_book")
     */
    public function updateAction(Request $request) {
        $postData = $request->request;
        $book = $this->getDoctrine()->getRepository('AppBundle:Book')
                ->find($postData->get("id"));

        $book->setCoverUrl($postData->get("cover"));
        $book->setTitle($postData->get("title"));
        $book->setAuthor($postData->get("author"));
        $book->setPrice($postData->get("price"));

        $em = $this->getDoctrine()->getManager();
        $em->merge($book);
        $em->flush();

        return $this->forward('AppBundle:Book:index');
    }

eugeniaperez.es

DOCTRINE ORM

TAREAS:

  • Acabar la práctica de los viajes (CRUD)
  • Empezar con los ejercicios de UT.9 Formularios (pág. 17)
Made with Slides.com