Tutorial Jobeet con Symfony2 Día 4: La Vista y el Controlador

De WikiSalud
Saltar a: navegación, buscar
Este artículo es parte del proyecto Tutorial Jobeet con Symfony2

Hoy vamos a personalizar el controlador job básico creado ayer. Ya tiene la mayoría del código necesario para Jobeet:

  • Una pagina para listar todas las ofertas de trabajo
  • Una página para crear nuevas ofertas
  • Una pagina para actualizar ofertas existentes
  • Una página para borrar una oferta

Aunque el código esta listo para ser usado como esta, refactorizaremos las plantillas para adaptarse más a las maquetas de Jobeet.

Contenido

La arquitectura MVC

Para desarrollo web, la solución más común para organizar tu código hoy en día es el patrón de diseño MVC. En breve, el patrón de diseño MVC define una forma para organizar tu código de acuerdo a su naturaleza. Este código separa el código en tres capas

  • La capa modelo define la logica de negocios, (La base de datos perteneces a esta capa). Usted ya sabe que Symfony2 almacena todas las clases y ficheros relacionados al modelo en el directorio Entity/ de tus bundles
  • La Vista es con lo que el usuario intectúa (Un motor de plantillas es parte de esta capa). En Symfony2, la capa de Vista esta principalmente hecha de Twig templates. Están almacenados en varios directorios Resources/views/, como veremos más tarde en estas líneas
  • El controlador es una pieza de código que llama al modelo para conseguir algunos datos que los pasará a la vista para renderizar al cliente. Cuando instalamos symfony al principio de este tutorial, vimos que todas las peticiones son gestionadas por Controladores Frontales (app.php y app_dev.php) Estos controladores Frontales delegan el trabajo real de las acciones

El diseño

Si echas un vistazo más cercano a las maquetas, notarás que gran parte de estas página lucen igual. Usted ya sabe que la duplicación de código es mala, si estamos hablando sobre código HTML o PHP, así que necesitamos encontrar una forma de prevenir estar elementos comunes de Vista que resultan en duplicación de código. Una forma para resolver este problema es definir una cabecera y un pie de página e incluirlos en cada plantilla. Una mejor forma es usar otro patrón de diseño para resolver este problema: El patrón de diseño Decorador. El patrón de diseño decorador resuelve los problemas al revés: La plantilla es decorada después que el contenido es renderizada por una plantilla global, llamada layout A diferencia de Symfony 1.x, Symfony2 no viene con un layout por defecto, pero crearemos uno y lo usaremos para decorar nuestras paginas de aplicación

Crea una nuevo fichero layout.html.twig en el directorio src/Ens/JobeetBundle/Resources/views/ y coloca el siguiente código

src/Ens/JobeetBundle/Resources/views/layout.html.twig
<!DOCTYPE html>
<html>
  <head>
    <title>
      {% block title %}
        Jobeet - Your best job board
      {% endblock %}
    </title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    {% block stylesheets %}
      <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/main.css') }}" type="text/css" media="all" />
    {% endblock %}
    {% block javascripts %}
    {% endblock %}
    <link rel="shortcut icon" href="{{ asset('bundles/ensjobeet/images/favicon.ico') }}" />
  </head>
  <body>
    <div id="container">
      <div id="header">
        <div class="content">
          <h1><a href="{{ path('job') }}">
            <img src="{{ asset('bundles/ensjobeet/images/logo.jpg') }}" alt="Jobeet Job Board" />
          </a></h1>
 
          <div id="sub_header">
            <div class="post">
              <h2>Ask for people</h2>
              <div>
                <a href="{{ path('job') }}">Post a Job</a>
              </div>
            </div>
 
            <div class="search">
              <h2>Ask for a job</h2>
              <form action="" method="get">
                <input type="text" name="keywords" id="search_keywords" />
                <input type="submit" value="search" />
                <div class="help">
                  Enter some keywords (city, country, position, ...)
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>
 
      <div id="content">
        {% if app.session.flashBag.has('notice') %}
          <div class="flash_notice">
            {{ app.session.flash('notice') }}
          </div>
        {% endif %}
 
        {% if app.session.flashBag.has('error') %}
          <div class="flash_error">
            {{ app.session.flash('error') }}
          </div>
        {% endif %}
 
        <div class="content">
            {% block content %}
            {% endblock %}
        </div>
      </div>
 
      <div id="footer">
        <div class="content">
          <span class="symfony">
            <img src="{{ asset('bundles/ensjobeet/images/jobeet-mini.png') }}" />
            powered by <a href="http://www.symfony.com/">
              <img src="{{ asset('bundles/ensjobeet/images/symfony.gif') }}" alt="symfony framework" />
            </a>
          </span>
          <ul>
            <li><a href="">About Jobeet</a></li>
            <li class="feed"><a href="">Full feed</a></li>
            <li><a href="">Jobeet API</a></li>
            <li class="last"><a href="">Affiliates</a></li>
          </ul>
        </div>
      </div>
    </div>
  </body>
</html>

Bloques Twig

En twig, el motor de plantillas por defecto, puedes definir bloques como lo hicimos anteriormente. Un bloque twig puede tener a contenido por defecto (Mira el bloque title por ejemplo) que puede ser reemplazado o extendido en la plantilla hija como verás en un momento

Ahora, haremos uso del nuevo layout creado, necesitaremos editar todos las plantillas en job (edit, index new y show de src/En/JobeetBundle/Resources/views/Job/) para extender la plantilla padre (el layout) y para sobreescribir el bloque de contenido que definimos en el:

{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block content %}
  <!-- original template code goes here -->
{% endblock %}

Las Hojas de estilo, Imágenes y Javascript

Como este tutorial no es sobre diseño web, ya hemos preparado todos los recursos que necesitaremos para Jobeet: Descarga los archivos de imagen y colocalos en el directorio src/Ens/JobeetBundle/Resources/public/images/, descarga los archivos de hojas de estilo y colocalos en el directorio src/Ens/JobeetBundle/Resources/public/css/

Ahora ejecuta

php app/console assets:install web

Para decirle a Symfony que los haga disponible al público

Si miras en el folder css notarás que tenemos cuatros archivos css: admin.css, job.css, jobs.css y main.css. El main.css es necesario en todas las páginas Jobeet así que las incluiremos en el bloque twig de hojas de estilo del layout. El resto son archivos css más especializados y los necesitamos sólo en páginas específicas

Para añadir un nuevo archivo css en una plantilla, sobreescribiremos el bloque de hojas de estilo, pero llamaremos a la plantilla padre antes de añadir el nuevo archivo css (Así tendríamos el archivo main.css y el archivo css adicional que necesitamos)

src/Ens/JobeetBundle/Resources/views/Job/index.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
<!-- the rest of the code -->
src/Ens/JobeetBundle/Resources/views/Job/show.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/show.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/job.css') }}" type="text/css" media="all" />
{% endblock %}
 
<!-- the rest of the code -->

El controlador de la página principal Job

Cada acción es representado por un método de una clase. Para la página de inicio, la clase es JobController y el método es indexAction(). Extrae todas las ofertas de trabajo de la base de datos

src/Ens/JobeetBundle/Controller/JobController.php
public function indexAction()
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $entities = $em->getRepository('EnsJobeetBundle:Job')->findAll();
 
    return $this->render('EnsJobeetBundle:Job:index.html.twig', array(
        'entities' => $entities
    ));
}

Echemos un vistazo más cerca al código: el método indexAction() obtiene el objeto gestor de entidades Doctrine, el cual es responsable de manipular los procesos de persistencia y extracción de objetos para y de la base de datos, y entonces el repositorio creará un consulta para recuperar todas las ofertas de trabajo. Retorna una ArrayCollection Doctrine de Objetos Job que son pasados a la plantilla (La vista)

La plantilla de la página principal Job

La plantilla index.html.twig genera un tabla html para todos los trabajo. He aquí el actual código de la plantilla

src/Ens/JobeetBundle/Resources/views/Job/index.html.twig
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
    <h1>Job list</h1>
 
    <table class="records_list">
        <thead>
            <tr>
                <th>Id</th>
                <th>Type</th>
                <th>Company</th>
                <th>Logo</th>
                <th>Url</th>
                <th>Position</th>
                <th>Location</th>
                <th>Description</th>
                <th>How_to_apply</th>
                <th>Token</th>
                <th>Is_public</th>
                <th>Is_activated</th>
                <th>Email</th>
                <th>Expires_at</th>
                <th>Created_at</th>
                <th>Updated_at</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
        {% for entity in entities %}
            <tr>
                <td><a href="{{ path('ens_job_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
                <td>{{ entity.type }}</td>
                <td>{{ entity.company }}</td>
                <td>{{ entity.logo }}</td>
                <td>{{ entity.url }}</td>
                <td>{{ entity.position }}</td>
                <td>{{ entity.location }}</td>
                <td>{{ entity.description }}</td>
                <td>{{ entity.howtoapply }}</td>
                <td>{{ entity.token }}</td>
                <td>{{ entity.ispublic }}</td>
                <td>{{ entity.isactivated }}</td>
                <td>{{ entity.email }}</td>
                <td>{% if entity.expiresat %}{{ entity.expiresat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>{% if entity.createdat %}{{ entity.createdat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>{% if entity.updatedat %}{{ entity.updatedat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>
                    <ul>
                        <li>
                            <a href="{{ path('ens_job_show', { 'id': entity.id }) }}">show</a>
                        </li>
                        <li>
                            <a href="{{ path('ens_job_edit', { 'id': entity.id }) }}">edit</a>
                        </li>
                    </ul>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
 
    <ul>
        <li>
            <a href="{{ path('ens_job_new') }}">
                Create a new entry
            </a>
        </li>
    </ul>
{% endblock %}

Limpiemos un poco para mostrar solo un subconjunto de las columnas disponibles. Reemplaza el bloque de contenido twig con el de abajo

src/Ens/JobeetBundle/Resources/views/Job/index.html.twig
{% block content %}
    <div id="jobs">
      <table class="jobs">
        {% for entity in entities %}
          <tr class="{{ cycle(['even', 'odd'], loop.index) }}">
            <td class="location">{{ entity.location }}</td>
            <td class="position">
              <a href="{{ path('job_show', { 'id': entity.id }) }}">
                {{ entity.position }}
              </a>
            </td>
            <td class="company">{{ entity.company }}</td>
          </tr>
        {% endfor %}
      </table>
    </div>
{% endblock %}

Por ahora, su página principal debe verse de esta forma

Vista del diseño final

Plantilla de la página Job

Personalicemos la plantilla de la pagina job. Abre el archivo show.html.twig y reemplaza su contenido con el código siguiente

src/Ens/JobeetBundle/Resources/views/Job/show.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/show.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
    {{ entity.company }} is looking for a {{ entity.position }}
{% endblock %}
 
{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/job.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
    <div id="job">
      <h1>{{ entity.company }}</h1>
      <h2>{{ entity.location }}</h2>
      <h3>
        {{ entity.position }}
        <small> - {{ entity.type }}</small>
      </h3>
 
      {% if entity.logo %}
        <div class="logo">
          <a href="{{ entity.url }}">
            <img src="/uploads/jobs/{{ entity.logo }}"
             alt="{{ entity.company }} logo" />
          </a>
        </div>
      {% endif %}
 
      <div class="description">
        {{ entity.description|nl2br }}
      </div>
 
      <h4>How to apply?</h4>
 
      <p class="how_to_apply">{{ entity.howtoapply }}</p>
 
      <div class="meta">
        <small>posted on {{ entity.createdat|date('m/d/Y') }}</small>
      </div>
 
      <div style="padding: 20px 0">
        <a href="{{ path('job_edit', { 'id': entity.id }) }}">
          Edit
        </a>
      </div>
    </div>
{% endblock %}

Por el momento, su página de detalle debe verse de la siguiente forma:

Primera vista04.png

El controlador de la página Job

La página job es generada por la acción show, definida en el método showAction() de JobController

src/Ens/JobeetBundle/Controller/JobController.php
public function showAction($id)
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $entity = $em->getRepository('EnsJobeetBundle:Job')->find($id);
 
    if (!$entity) {
        throw $this->createNotFoundException('Unable to find Job entity.');
    }
 
    $deleteForm = $this->createDeleteForm($id);
 
    return $this->render('EnsJobeetBundle:Job:show.html.twig', array(
        'entity'      => $entity,
        'delete_form' => $deleteForm->createView(),
 
    ));
}

Como en la acción de index, la clase repositorio EnsJobeetBundle:Job es usada para recuperar una oferta de trabajo, esta vez usando el metodo find(). El parametro de este método es el identificador único de un trabajo, su llave primaria. La siguiente sección explicará porque el parametro $id de la función actionShow() contiene la llave primaria job.

Si el trabajo no existe en la base de datos, queremos enviar al usuario a una página 404 , lo cua es exactamente lo que la throw $this->createNotFoundException()

En cuanto a las excepciones, la página que se muestra al usuario es diferente en el entorno de producción y en el entorno de desarrollo

Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas