{"id":2176,"date":"2013-08-10T14:08:17","date_gmt":"2013-08-10T14:08:17","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2176"},"modified":"2024-05-31T09:05:07","modified_gmt":"2024-05-31T09:05:07","slug":"symfony2-jobeet-day-4-the-controller-and-the-view","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/","title":{"rendered":"Symfony2 Jobeet Day 4: The Controller and the View"},"content":{"rendered":"<p><span style=\"font-family: timesnew roman; font-size: 12px;\">* This article is part of the original <a href=\"http:\/\/symfony.com\/legacy\/doc\/jobeet?orm=Doctrine\" target=\"_blank\" rel=\"noopener\">Jobeet Tutorial<\/a>, created by Fabien Potencier, for Symfony 1.4.<\/span><\/p>\n<p>Today, we are going to customize the basic <strong>job controller<\/strong> we created yesterday. It already has most of the code we need for Jobeet:<\/p>\n<ul>\n<li>A page to<strong> list<\/strong> all jobs<\/li>\n<li>A page to <strong>create<\/strong> a new job<\/li>\n<li>A page to <strong>update<\/strong> an existing job<\/li>\n<li>A page to <strong>delete<\/strong> a job<span id=\"more-182\"><\/span><\/li>\n<\/ul>\n<p>Although the code is ready to be used as is, we will refactor the templates to match closer to the Jobeet mockups.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_MVC_Arhitecture\" title=\"The MVC Arhitecture\">The MVC Arhitecture<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Layout\" title=\"The Layout\">The Layout<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#Twig_Blocks\" title=\"Twig Blocks\">Twig Blocks<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Stylesheets_Images_and_JavaScripts\" title=\"The Stylesheets, Images and JavaScripts\">The Stylesheets, Images and JavaScripts<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Job_Homepage_Action\" title=\"The Job Homepage Action\">The Job Homepage Action<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Job_Homepage_Template\" title=\"The Job Homepage Template\">The Job Homepage Template<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Job_Page_Template\" title=\"The Job Page Template\">The Job Page Template<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-4-the-controller-and-the-view\/#The_Job_Page_Action\" title=\"The Job Page Action\">The Job Page Action<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"The_MVC_Arhitecture\"><\/span>The MVC Arhitecture<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>For web development, the most common solution for organizing your code nowadays is the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Model-view-controller\" target=\"_blank\" rel=\"noopener\">MVC design pattern<\/a>. In short, the MVC design pattern defines a way to organize your code according to its nature. This pattern separates the code into <strong>three layers<\/strong>:<\/p>\n<ul>\n<li>The <strong>Model<\/strong> layer defines the business logic (the database belongs to this layer). You already know that Symfony stores all the classes and files related to the Model in the <code>Entity\/<\/code> directory of your bundles.<\/li>\n<li>The<strong> View<\/strong> is what the user interacts with (a template engine is part of this layer). In Symfony 2.3.2, the View layer is mainly made of Twig templates. They are stored in various <code>Resources\/views\/<\/code> directories as we will see later in these lines.<\/li>\n<li>The <strong>Controller<\/strong> is a piece of code that calls the Model to get some data that it passes to the View for rendering to the client. When we installed Symfony at the beginning of this tutorial, we saw that all requests are managed by front controllers (<code>app.php<\/code> and <code>app_dev.php<\/code>). These front controllers delegate the real work to <strong>actions<\/strong>.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"The_Layout\"><\/span>The Layout<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you have a closer look at the mockups, you will notice that much of each page looks the same. You already know that code duplication is bad, whether we are talking about HTML or PHP code, so we need to find a way to prevent these common view elements from resulting in code duplication.<\/p>\n<p>One way to solve the problem is to define a header and a footer and include them in each template. A better way is to use another design pattern to solve this problem: the decorator design pattern. The decorator design pattern resolves the problem the other way around: the template is decorated after the content is rendered by a global template, called a <strong>layout<\/strong>.<\/p>\n<p>Symfony2 does not came with a default layout, so we will create one and use it to decorate our application pages.<\/p>\n<p>Create a new file <code>layout.html.twig<\/code> in the <code>src\/Ibw\/JobeetBundle\/Resources\/views\/<\/code> directory and put in the following code:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/layout.html.twig\">&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n    &lt;head&gt;\r\n        &lt;title&gt;\r\n            {% block title %}\r\n                Jobeet - Your best job board\r\n            {% endblock %}\r\n        &lt;\/title&gt;\r\n        &lt;meta http-equiv=\"Content-Type\" content=\"text\/html; charset=utf-8\" \/&gt;\r\n        {% block stylesheets %}\r\n            &lt;link rel=\"stylesheet\" href=\"{{ asset('bundles\/ibwjobeet\/css\/main.css') }}\" type=\"text\/css\" media=\"all\" \/&gt;\r\n        {% endblock %}\r\n        {% block javascripts %}\r\n        {% endblock %}\r\n        &lt;link rel=\"shortcut icon\" href=\"{{ asset('bundles\/ibwjobeet\/images\/favicon.ico') }}\" \/&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;div id=\"container\"&gt;\r\n            &lt;div id=\"header\"&gt;\r\n                &lt;div class=\"content\"&gt;\r\n                    &lt;h1&gt;&lt;a href=\"{{ path('ibw_job') }}\"&gt;\r\n                        &lt;img src=\"{{ asset('bundles\/ibwjobeet\/images\/logo.jpg') }}\" alt=\"Jobeet Job Board\" \/&gt;\r\n                    &lt;\/a&gt;&lt;\/h1&gt;\r\n\r\n                    &lt;div id=\"sub_header\"&gt;\r\n                        &lt;div class=\"post\"&gt;\r\n                            &lt;h2&gt;Ask for people&lt;\/h2&gt;\r\n                            &lt;div&gt;\r\n                                &lt;a href=\"{{ path('ibw_job') }}\"&gt;Post a Job&lt;\/a&gt;\r\n                            &lt;\/div&gt;\r\n                        &lt;\/div&gt;\r\n\r\n                        &lt;div class=\"search\"&gt;\r\n                            &lt;h2&gt;Ask for a job&lt;\/h2&gt;\r\n                            &lt;form action=\"\" method=\"get\"&gt;\r\n                                &lt;input type=\"text\" name=\"keywords\" id=\"search_keywords\" \/&gt;\r\n                                &lt;input type=\"submit\" value=\"search\" \/&gt;\r\n                                &lt;div class=\"help\"&gt;\r\n                                    Enter some keywords (city, country, position, ...)\r\n                                &lt;\/div&gt;\r\n                            &lt;\/form&gt;\r\n                        &lt;\/div&gt;\r\n                    &lt;\/div&gt;\r\n                &lt;\/div&gt;\r\n            &lt;\/div&gt;\r\n\r\n           &lt;div id=\"content\"&gt;\r\n               {% for flashMessage in app.session.flashbag.get('notice') %}\r\n                   &lt;div class=\"flash_notice\"&gt;\r\n                       {{ flashMessage }}\r\n                   &lt;\/div&gt;\r\n               {% endfor %}\r\n\r\n               {% for flashMessage in app.session.flashbag.get('error') %}\r\n                   &lt;div class=\"flash_error\"&gt;\r\n                       {{ flashMessage }}\r\n                   &lt;\/div&gt;\r\n               {% endfor %}\r\n\r\n               &lt;div class=\"content\"&gt;\r\n                   {% block content %}\r\n                   {% endblock %}\r\n               &lt;\/div&gt;\r\n           &lt;\/div&gt;\r\n\r\n           &lt;div id=\"footer\"&gt;\r\n               &lt;div class=\"content\"&gt;\r\n                   &lt;span class=\"symfony\"&gt;\r\n                       &lt;img src=\"{{ asset('bundles\/ibwjobeet\/images\/jobeet-mini.png') }}\" \/&gt;\r\n                           powered by &lt;a href=\"http:\/\/www.symfony.com\/\"&gt;\r\n                           &lt;img src=\"{{ asset('bundles\/ibwjobeet\/images\/symfony.gif') }}\" alt=\"symfony framework\" \/&gt;\r\n                       &lt;\/a&gt;\r\n                   &lt;\/span&gt;\r\n                   &lt;ul&gt;\r\n                       &lt;li&gt;&lt;a href=\"\"&gt;About Jobeet&lt;\/a&gt;&lt;\/li&gt;\r\n                       &lt;li class=\"feed\"&gt;&lt;a href=\"\"&gt;Full feed&lt;\/a&gt;&lt;\/li&gt;\r\n                       &lt;li&gt;&lt;a href=\"\"&gt;Jobeet API&lt;\/a&gt;&lt;\/li&gt;\r\n                       &lt;li class=\"last\"&gt;&lt;a href=\"\"&gt;Affiliates&lt;\/a&gt;&lt;\/li&gt;\r\n                   &lt;\/ul&gt;\r\n               &lt;\/div&gt;\r\n           &lt;\/div&gt;\r\n       &lt;\/div&gt;\r\n   &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Twig_Blocks\"><\/span>Twig Blocks<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In Twig, the default Symfony template engine, you can define <strong>blocks<\/strong> as we did above. A twig block can have a default content (look at the <code>title block<\/code>, for example) that can be replaced or extended in the child template as you will see in a moment.<\/p>\n<p>Now, to make use of the layout we created, we will need to edit all the job templates (<code><\/code><code>index, edit, new<\/code> and <code>show<\/code> from <code>src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/<\/code>) to extend the parent template (the layout) and to overwrite the <code>content<\/code> block we defined with the <code>body<\/code> block content from the original template<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 lang:default decode:true \">{% extends 'IbwJobeetBundle::layout.html.twig' %}\r\n\r\n{% block content %}\r\n    &lt;!-- original body block code goes here --&gt;\r\n{% endblock %}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_Stylesheets_Images_and_JavaScripts\"><\/span>The Stylesheets, Images and JavaScripts<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>As this is not about web design, we have already prepared all the needed assets we will use for Jobeet: download the image files archive and put them into the <code>src\/Ibw\/JobeetBundle\/Resources\/public\/images\/<\/code> directory; download the stylesheet files archive and put them into the <code>src\/Ibw\/JobeetBundle\/Resources\/public\/css\/<\/code> directory.<\/p>\n<p>Now run<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false nums-toggle:false lang:default decode:true \">php app\/console assets:install web --symlink<\/pre>\n<p>to tell Symfony to make them available to the public.<\/p>\n<p>If you look in the <code>css<\/code> folder, you will notice that we have four css files: <code>admin.css<\/code>, <code>job.css<\/code>,<code>jobs.css<\/code> and <code>main.css<\/code>. The <code>main.css<\/code> is needed in all Jobeet pages, so we included it in the layout in the <code>stylesheet twig block<\/code>. The rest are more specialized css files and we need them only in specific pages.<\/p>\n<p>To add a new css file in a template, we will overwrite the stylesheet block, but call the parent before adding the new css file (so we would have the main.css and the additional css files we need).<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/index.html.twig\">{% extends 'IbwJobeetBundle::layout.html.twig' %}\r\n\r\n{% block stylesheets %}\r\n    {{ parent() }}\r\n    &lt;link rel=\"stylesheet\" href=\"{{ asset('bundles\/ibwjobeet\/css\/jobs.css') }}\" type=\"text\/css\" media=\"all\" \/&gt;\r\n{% endblock %}\r\n\r\n&lt;!-- rest of the code --&gt;<\/pre>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/show.html.twig\">{% extends 'IbwJobeetBundle::layout.html.twig' %}\r\n\r\n{% block stylesheets %}\r\n    {{ parent() }}\r\n    &lt;link rel=\"stylesheet\" href=\"{{ asset('bundles\/ibwjobeet\/css\/job.css') }}\" type=\"text\/css\" media=\"all\" \/&gt;\r\n{% endblock %}\r\n\r\n&lt;!-- rest of the code --&gt;<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"The_Job_Homepage_Action\"><\/span>The Job Homepage Action<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Each action is represented by a method of a class. For the job homepage, the class is <code>JobController<\/code> and the method is <code>indexAction()<\/code>. It retrieves all the jobs from the database.<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 wrap:true lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function indexAction()\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n\r\n    $entities = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;findAll();\r\n\r\n    return $this-&gt;render('IbwJobeetBundle:Job:index.html.twig', array(\r\n        'entities' =&gt; $entities\r\n    ));\r\n}\r\n\r\n\/\/ ...<\/pre>\n<p>Let\u2019s have a closer look at the code: the <code>indexAction()<\/code> method gets the <strong>Doctrine entity manager<\/strong> object, which is responsible for handling the process of persisting and fetching objects to and from database, and then the <strong>repository<\/strong>, that will create a query to retrieve all the jobs. It returns a Doctrine <code>ArrayCollection<\/code> of <code>Job<\/code> objects that are passed to the template (the View).<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Job_Homepage_Template\"><\/span>The Job Homepage Template<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The <code>index.html.twig<\/code> template generates an HTML table for all the jobs. Here is the current template code:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/index.html.twig\">{% extends 'IbwJobeetBundle::layout.html.twig' %}\r\n\r\n{% block stylesheets %}\r\n    {{ parent() }}\r\n    &lt;link rel=\"stylesheet\" href=\"{{ asset('bundles\/ibwjobeet\/css\/jobs.css') }}\" type=\"text\/css\" media=\"all\" \/&gt;\r\n{% endblock %}\r\n\r\n{% block content %}\r\n    &lt;h1&gt;Job list&lt;\/h1&gt;\r\n\r\n    &lt;table class=\"records_list\"&gt;\r\n        &lt;thead&gt;\r\n            &lt;tr&gt;\r\n                &lt;th&gt;Id&lt;\/th&gt;\r\n                &lt;th&gt;Type&lt;\/th&gt;\r\n                &lt;th&gt;Company&lt;\/th&gt;\r\n                &lt;th&gt;Logo&lt;\/th&gt;\r\n                &lt;th&gt;Url&lt;\/th&gt;\r\n                &lt;th&gt;Position&lt;\/th&gt;\r\n                &lt;th&gt;Location&lt;\/th&gt;\r\n                &lt;th&gt;Description&lt;\/th&gt;\r\n                &lt;th&gt;How_to_apply&lt;\/th&gt;\r\n                &lt;th&gt;Token&lt;\/th&gt;\r\n                &lt;th&gt;Is_public&lt;\/th&gt;\r\n                &lt;th&gt;Is_activated&lt;\/th&gt;\r\n                &lt;th&gt;Email&lt;\/th&gt;\r\n                &lt;th&gt;Expires_at&lt;\/th&gt;\r\n                &lt;th&gt;Created_at&lt;\/th&gt;\r\n                &lt;th&gt;Updated_at&lt;\/th&gt;\r\n                &lt;th&gt;Actions&lt;\/th&gt;\r\n            &lt;\/tr&gt;\r\n        &lt;\/thead&gt;\r\n        &lt;tbody&gt;\r\n        {% for entity in entities %}\r\n            &lt;tr&gt;\r\n                &lt;td&gt;&lt;a href=\"{{ path('ibw_job_show', { 'id': entity.id }) }}\"&gt;{{ entity.id }}&lt;\/a&gt;&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.type }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.company }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.logo }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.url }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.position }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.location }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.description }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.howtoapply }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.token }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.ispublic }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.isactivated }}&lt;\/td&gt;\r\n                &lt;td&gt;{{ entity.email }}&lt;\/td&gt;\r\n                &lt;td&gt;{% if entity.expiresat %}{{ entity.expiresat|date('Y-m-d H:i:s') }}{% endif%}&lt;\/td&gt;\r\n                &lt;td&gt;{% if entity.createdat %}{{ entity.createdat|date('Y-m-d H:i:s') }}{% endif%}&lt;\/td&gt;\r\n                &lt;td&gt;{% if entity.updatedat %}{{ entity.updatedat|date('Y-m-d H:i:s') }}{% endif%}&lt;\/td&gt;\r\n                &lt;td&gt;\r\n                    &lt;ul&gt;\r\n                        &lt;li&gt;\r\n                            &lt;a href=\"{{ path('ibw_job_show', { 'id': entity.id }) }}\"&gt;show&lt;\/a&gt;\r\n                        &lt;\/li&gt;\r\n                        &lt;li&gt;\r\n                            &lt;a href=\"{{ path('ibw_job_edit', { 'id': entity.id }) }}\"&gt;edit &lt;\/a&gt;\r\n                        &lt;\/li&gt;\r\n                    &lt;\/ul&gt;\r\n                &lt;\/td&gt;\r\n            &lt;\/tr&gt;\r\n        {% endfor %}\r\n        &lt;\/tbody&gt;\r\n    &lt;\/table&gt;\r\n\r\n    &lt;ul&gt;\r\n        &lt;li&gt;\r\n            &lt;a href=\"{{ path('ibw_job_new') }}\"&gt;\r\n                Create a new entry\r\n            &lt;\/a&gt;\r\n        &lt;\/li&gt;\r\n    &lt;\/ul&gt;\r\n{% endblock %}<\/pre>\n<p>Let\u2019s clean this up a bit to only display a sub-set of the available columns. Replace the <code>twig block content<\/code> with the one below:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 lang:default decode:true \">{% block content %}\r\n    &lt;div id=\"jobs\"&gt;\r\n        &lt;table class=\"jobs\"&gt;\r\n            {% for entity in entities %}\r\n                &lt;tr class=\"{{ cycle(['even', 'odd'], loop.index) }}\"&gt;\r\n                    &lt;td class=\"location\"&gt;{{ entity.location }}&lt;\/td&gt;\r\n                    &lt;td class=\"position\"&gt;\r\n                        &lt;a href=\"{{ path('ibw_job_show', { 'id': entity.id }) }}\"&gt;\r\n                            {{ entity.position }}\r\n                        &lt;\/a&gt;\r\n                    &lt;\/td&gt;\r\n                    &lt;td class=\"company\"&gt;{{ entity.company }}&lt;\/td&gt;\r\n                &lt;\/tr&gt;\r\n            {% endfor %}\r\n        &lt;\/table&gt;\r\n    &lt;\/div&gt;\r\n{% endblock %}<\/pre>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-2-jobs.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-42\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-2-jobs.png\" alt=\"Day 4 - 2 jobs\" width=\"1183\" height=\"776\" \/><\/a><\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Job_Page_Template\"><\/span>The Job Page Template<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now let\u2019s customize the template of the job page. Open the <code>show.html.twig<\/code> file and replace its content with the following code:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/show.html.twig\">{% extends 'IbwJobeetBundle::layout.html.twig' %}\r\n\r\n{% block title %}\r\n    {{ entity.company }} is looking for a {{ entity.position }}\r\n{% endblock %}\r\n\r\n{% block stylesheets %}\r\n    {{ parent() }}\r\n    &lt;link rel=\"stylesheet\" href=\"{{ asset('bundles\/ibwjobeet\/css\/job.css') }}\" type=\"text\/css\" media=\"all\" \/&gt;\r\n{% endblock %}\r\n\r\n{% block content %}\r\n    &lt;div id=\"job\"&gt;\r\n        &lt;h1&gt;{{ entity.company }}&lt;\/h1&gt;\r\n        &lt;h2&gt;{{ entity.location }}&lt;\/h2&gt;\r\n        &lt;h3&gt;\r\n            {{ entity.position }}\r\n            &lt;small&gt; - {{ entity.type }}&lt;\/small&gt;\r\n        &lt;\/h3&gt;\r\n\r\n        {% if entity.logo %}\r\n            &lt;div class=\"logo\"&gt;\r\n                &lt;a href=\"{{ entity.url }}\"&gt;\r\n                    &lt;img src=\"\/uploads\/jobs\/{{ entity.logo }}\"\r\n                        alt=\"{{ entity.company }} logo\" \/&gt;\r\n                &lt;\/a&gt;\r\n            &lt;\/div&gt;\r\n        {% endif %}\r\n\r\n        &lt;div class=\"description\"&gt;\r\n            {{ entity.description|nl2br }}\r\n        &lt;\/div&gt;\r\n\r\n        &lt;h4&gt;How to apply?&lt;\/h4&gt;\r\n\r\n        &lt;p class=\"how_to_apply\"&gt;{{ entity.howtoapply }}&lt;\/p&gt;\r\n\r\n        &lt;div class=\"meta\"&gt;\r\n            &lt;small&gt;posted on {{ entity.createdat|date('m\/d\/Y') }}&lt;\/small&gt;\r\n        &lt;\/div&gt;\r\n\r\n        &lt;div style=\"padding: 20px 0\"&gt;\r\n            &lt;a href=\"{{ path('ibw_job_edit', { 'id': entity.id }) }}\"&gt;\r\n                Edit\r\n            &lt;\/a&gt;\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n{% endblock %}<\/pre>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-individual-job.png\"><img decoding=\"async\" class=\"alignnone wp-image-43 size-full\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-individual-job.png\" alt=\"Day 4 - individual job\" width=\"1183\" height=\"776\" \/><\/a><\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_Job_Page_Action\"><\/span>The Job Page Action<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The job page is generated by the <code>show<\/code> action, defined in the <code>showAction()<\/code> method of the <code>JobController<\/code>:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">public function showAction($id)\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n\r\n    $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;find($id);\r\n\r\n    if (!$entity) {\r\n        throw $this-&gt;createNotFoundException('Unable to find Job entity.');\r\n    }\r\n\r\n    $deleteForm = $this-&gt;createDeleteForm($id);\r\n\r\n    return $this-&gt;render('IbwJobeetBundle:Job:show.html.twig', array(\r\n        'entity' =&gt; $entity,\r\n        'delete_form' =&gt; $deleteForm-&gt;createView(),\r\n    ));\r\n}<\/pre>\n<p>As in the <code>index<\/code> action, the <code>IbwJobeetBundle<\/code> repository class is used to retrieve a job, this time using the <code>find()<\/code> method. The parameter of this method is the unique identifier of a job, its primary key. The next section will explain why the <code>$id<\/code> parameter of the <code>actionShow() <\/code>function contains the job primary key.<\/p>\n<p>If the job does not exist in the database, we want to forward the user to a 404 page, which is exactly what the <code>throw $this-&gt;createNotFoundException()<\/code> does.<\/p>\n<p>As for exceptions, the page displayed to the user is different in the <code>prod<\/code> environment and in the <code>dev<\/code> ennvironment.<\/p>\n<p><span style=\"line-height: 1.714285714; font-size: 1rem;\"><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-error1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-44\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-error1.png\" alt=\"Day 4 - error1\" width=\"1183\" height=\"776\" \/><\/a><\/span><\/p>\n<p><span style=\"line-height: 1.714285714; font-size: 1rem;\"><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-error2.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-45\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-4-error2.png\" alt=\"Day 4 - error2\" width=\"1183\" height=\"776\" \/><\/a><\/span><\/p>\n<h4>That\u2019s all for today! Tomorrow we will get you familiar with the routing features.<\/h4>\n<p><a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" rel=\"license\"><img decoding=\"async\" style=\"border-width: 0;\" src=\"\/\/i.creativecommons.org\/l\/by-sa\/3.0\/88x31.png\" alt=\"Creative Commons License\" \/><\/a><br \/>\nThis work is licensed under a <a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" target=\"_blank\" rel=\"license noopener\">Creative Commons Attribution-ShareAlike 3.0 Unported License<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. Today, we are [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[],"yst_prominent_words":[1359,1368,1367,1366,1365,1364,1363,1362,1361,1360,6,1358,1357,1356,1355,1354,1353,1352,1351,394],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2176"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2176"}],"version-history":[{"count":4,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2176\/revisions"}],"predecessor-version":[{"id":133053,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2176\/revisions\/133053"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2176"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2176"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2176"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2176"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}