eugeniaperez.es
eugeniaperez.es
https://getcomposer.org/doc/00-intro.md#installation-windows
Descarga y ejecuta el instalador
Introduce la ruta del php.exe (en caso de que hayas instalado XAMPP estará en C:\xampp\php\php.exe)
Opciones por defecto
eugeniaperez.es
composer create-project symfony/framework-standard-edition project_name
Vamos a dejarlo
en htdocs.
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
/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
Crea el proyecto que acabamos de generar con
Composer en el Netbeans:
New Project -> PHP Application with existing sources
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
//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
eugeniaperez.es
/**
* @Route("/", name="blog_index", defaults={"page" = 1})
* @Route("/page/{page}", name="blog_index_paginated", requirements={"page" : "\d+"})
*/
public function indexAction($page)
{
...
}
eugeniaperez.es
app:
resource: "@AppBundle/Controller/"
type: annotation
eugeniaperez.es
/**
* @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
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
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
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
eugeniaperez.es
eugeniaperez.es
<!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
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hello world!</h1>
{% endblock %}
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
{% 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
<!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
eugeniaperez.es
eugeniaperez.es
//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
//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
eugeniaperez.es
eugeniaperez.es
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
{% 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
eugeniaperez.es
<?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
/**
* @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
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\Book;
eugeniaperez.es
{% 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
eugeniaperez.es
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
eugeniaperez.es
parameters:
database_host: 127.0.0.1
database_port: null
database_name: books
database_user: root
database_password: root
eugeniaperez.es
eugeniaperez.es
<?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
eugeniaperez.es
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
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
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
eugeniaperez.es
eugeniaperez.es
//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
eugeniaperez.es
form.navbar-right{
margin-right: 5em;
}
eugeniaperez.es
eugeniaperez.es
eugeniaperez.es
/**
* 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
eugeniaperez.es
/**
* 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
eugeniaperez.es
//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
/**
* 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
eugeniaperez.es
{% 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
eugeniaperez.es
/**
* 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
/**
* 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
<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
eugeniaperez.es
eugeniaperez.es
/**
* 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
eugeniaperez.es
/**
* 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
eugeniaperez.es
{% 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
eugeniaperez.es
/**
* 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