{"id":2250,"date":"2013-08-17T08:36:07","date_gmt":"2013-08-17T08:36:07","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2250"},"modified":"2024-09-30T07:46:11","modified_gmt":"2024-09-30T07:46:11","slug":"symfony2-jobeet-day-11-testing-your-forms","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-11-testing-your-forms\/","title":{"rendered":"Symfony2 Jobeet Day 11: Testing your Forms"},"content":{"rendered":"<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-11-testing-your-forms\/#_This_article_is_part_of_the_original_Jobeet_Tutorial_created_by_Fabien_Potencier_for_Symfony_14_Testing_your_forms_in_jobeet\" title=\"* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. \nTesting your forms in jobeet\">* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. \nTesting your forms in jobeet<\/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-11-testing-your-forms\/#Submitting_a_Form\" title=\"Submitting a Form\">Submitting a Form<\/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-11-testing-your-forms\/#Testing_the_Form\" title=\"Testing the Form\">Testing the Form<\/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-11-testing-your-forms\/#Testing_the_Database_Record\" title=\"Testing the Database Record\">Testing the Database Record<\/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-11-testing-your-forms\/#Testing_for_Errors\" title=\"Testing for Errors\">Testing for Errors<\/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-11-testing-your-forms\/#Tests_as_a_SafeGuard\" title=\"Tests as a SafeGuard\">Tests as a SafeGuard<\/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-11-testing-your-forms\/#Back_to_the_Future_in_a_Test\" title=\"Back to the Future in a Test\">Back to the Future in a Test<\/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-11-testing-your-forms\/#Maintenance_Tasks\" title=\"Maintenance Tasks\">Maintenance Tasks<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"_This_article_is_part_of_the_original_Jobeet_Tutorial_created_by_Fabien_Potencier_for_Symfony_14_Testing_your_forms_in_jobeet\"><\/span><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><br \/>\nTesting your forms in jobeet<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In day 10, we created our first form with Symfony 2.3. People are now able to post a new job on Jobeet but we ran out of time before we could add some tests. That\u2019s what we will do along these lines.<span id=\"more-189\"><\/span><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Submitting_a_Form\"><\/span>Submitting a Form<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s open the <code>JobControllerTest<\/code> file to add functional tests for the job creation and validation process. At the end of the file, add the following code to get the job creation page:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function testJobForm()\r\n{\r\n    $client = static::createClient();\r\n\r\n    $crawler = $client-&gt;request('GET', '\/job\/new');\r\n    $this-&gt;assertEquals('IbwJobeetBundleControllerJobController::newAction', $client-&gt;getRequest()-&gt;attributes-&gt;get('_controller'));\r\n}<\/pre>\n<p>To select forms we will use the <code>selectButton()<\/code> method. This method can select <code>button<\/code> tags and submit <code>input<\/code> tags. Once you have a Crawler representing a button, call the <code>form()<\/code> method to get a <code>Form<\/code> instance for the form wrapping the button node:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">$form = $crawler-&gt;selectButton('Submit Form')-&gt;form();<\/pre>\n<blockquote><p>The above example selects an input of type submit using its value attribute \u201cS<code>ubmit Form\"<\/code>.<\/p><\/blockquote>\n<p>When calling the <code>form()<\/code> method, you can also pass an array of field values that overrides the default ones:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">$form = $crawler-&gt;selectButton('submit')-&gt;form(array(\r\n    'name' =&gt; 'Fabien',\r\n    'my_form[subject]' =&gt; 'Symfony Rocks!'\r\n));<\/pre>\n<p>It is now time to actually select and pass valid values to the form:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function testJobForm()\r\n{\r\n    $client = static::createClient();\r\n\r\n    $crawler = $client-&gt;request('GET', '\/job\/new');\r\n    $this-&gt;assertEquals('IbwJobeetBundleControllerJobController::newAction', $client-&gt;getRequest()-&gt;attributes-&gt;get('_controller'));\r\n\r\n    $form = $crawler-&gt;selectButton('Preview your job')-&gt;form(array(\r\n        'job[company]'      =&gt; 'Sensio Labs',\r\n        'job[url]'          =&gt; 'http:\/\/www.sensio.com\/',\r\n        'job[file]'         =&gt; __DIR__.'\/..\/..\/..\/..\/..\/web\/bundles\/ibwjobeet\/images\/sensio-labs.gif',\r\n        'job[position]'     =&gt; 'Developer',\r\n        'job[location]'     =&gt; 'Atlanta, USA',\r\n        'job[description]'  =&gt; 'You will work with symfony to develop websites for our customers.',\r\n        'job[how_to_apply]' =&gt; 'Send me an email',\r\n        'job[email]'        =&gt; 'for.a.job@example.com',\r\n        'job[is_public]'    =&gt; false,\r\n    ));\r\n\r\n    $client-&gt;submit($form);\r\n    $this-&gt;assertEquals('IbwJobeetBundleControllerJobController::createAction', $client-&gt;getRequest()-&gt;attributes-&gt;get('_controller'));\r\n}<\/pre>\n<p>The browser also simulates file uploads if you pass the absolute path to the file to upload.<\/p>\n<p>After submitting the form, we checked that the executed action is <code>create<\/code>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Testing_the_Form\"><\/span>Testing the Form<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If the form is valid, the job should have been created and the user redirected to the <code>preview<\/code> page:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">public function testJobForm()\r\n{\r\n    \/\/ ...\r\n    $client-&gt;followRedirect();\r\n    $this-&gt;assertEquals('IbwJobeetBundleControllerJobController::previewAction', $client-&gt;getRequest()-&gt;attributes-&gt;get('_controller'));\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Testing_the_Database_Record\"><\/span>Testing the Database Record<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Eventually, we want to test that the job has been created in the database and check that the <code>is_activated<\/code> column is set to false as the user has not published it yet.<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">public function testJobForm()\r\n{\r\n    \/\/ ...\r\n    $kernel = static::createKernel();\r\n    $kernel-&gt;boot();\r\n    $em = $kernel-&gt;getContainer()-&gt;get('doctrine.orm.entity_manager');\r\n\r\n    $query = $em-&gt;createQuery('SELECT count(j.id) from IbwJobeetBundle:Job j WHERE j.location = :location AND j.is_activated IS NULL AND j.is_public = 0');\r\n    $query-&gt;setParameter('location', 'Atlanta, USA');\r\n    $this-&gt;assertTrue(0 &lt; $query-&gt;getSingleScalarResult());\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Testing_for_Errors\"><\/span>Testing for Errors<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The job form creation works as expected when we submit valid values. Let\u2019s add a test to check the behavior when we submit non-valid data:<\/p>\n<pre class=\"nums:false lang:default decode:true \">public function testJobForm()\r\n{\r\n    \/\/ ...\r\n    $crawler = $client-&gt;request('GET', '\/job\/new');\r\n    $form = $crawler-&gt;selectButton('Preview your job')-&gt;form(array(\r\n        'job[company]'      =&gt; 'Sensio Labs',\r\n        'job[position]'     =&gt; 'Developer',\r\n        'job[location]'     =&gt; 'Atlanta, USA',\r\n        'job[email]'        =&gt; 'not.an.email',\r\n    ));\r\n    $crawler = $client-&gt;submit($form);\r\n\r\n    \/\/ check if we have 3 errors\r\n    $this-&gt;assertTrue($crawler-&gt;filter('.error_list')-&gt;count() == 3);\r\n\r\n    \/\/ check if we have error on job_description field\r\n    $this-&gt;assertTrue($crawler-&gt;filter('#job_description')-&gt;siblings()-&gt;first()-&gt;filter('.error_list')-&gt;count() == 1);\r\n\r\n    \/\/ check if we have error on job_how_to_apply field\r\n    $this-&gt;assertTrue($crawler-&gt;filter('#job_how_to_apply')-&gt;siblings()-&gt;first()-&gt;filter('.error_list')-&gt;count() == 1);\r\n\r\n    \/\/ check if we have error on job_email field\r\n    $this-&gt;assertTrue($crawler-&gt;filter('#job_email')-&gt;siblings()-&gt;first()-&gt;filter('.error_list')-&gt;count() == 1);\r\n}<\/pre>\n<p>Now, we need to test the admin bar found on the job preview page. When a job has not been activated yet, you can <strong>edit<\/strong>, <strong>delete<\/strong>, or <strong>publish<\/strong> the job. To test those three actions, we will need to first create a job. But that\u2019s a lot of copy and paste, so let\u2019s add a job creator method in the <code>JobControllerTest<\/code> class:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function createJob($values = array())\r\n{\r\n    $client = static::createClient();\r\n    $crawler = $client-&gt;request('GET', '\/job\/new');\r\n    $form = $crawler-&gt;selectButton('Preview your job')-&gt;form(array_merge(array(\r\n        'job[company]'      =&gt; 'Sensio Labs',\r\n        'job[url]'          =&gt; 'http:\/\/www.sensio.com\/',\r\n        'job[position]'     =&gt; 'Developer',\r\n        'job[location]'     =&gt; 'Atlanta, USA',\r\n        'job[description]'  =&gt; 'You will work with symfony to develop websites for our customers.',\r\n        'job[how_to_apply]' =&gt; 'Send me an email',\r\n        'job[email]'        =&gt; 'for.a.job@example.com',\r\n        'job[is_public]'    =&gt; false,\r\n  ), $values));\r\n\r\n    $client-&gt;submit($form);\r\n    $client-&gt;followRedirect();\r\n\r\n    return $client;\r\n}<\/pre>\n<p>The <code>createJob()<\/code> method creates a job, follows the redirect and returns the browser. You can also pass an array of values that will be merged with some default values.<\/p>\n<p>Testing the <code>Publish<\/code> action is now more simple:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">public function testPublishJob()\r\n{\r\n    $client = $this-&gt;createJob(array('job[position]' =&gt; 'FOO1'));\r\n    $crawler = $client-&gt;getCrawler();\r\n    $form = $crawler-&gt;selectButton('Publish')-&gt;form();\r\n    $client-&gt;submit($form);\r\n\r\n    $kernel = static::createKernel();\r\n    $kernel-&gt;boot();\r\n    $em = $kernel-&gt;getContainer()-&gt;get('doctrine.orm.entity_manager');\r\n\r\n    $query = $em-&gt;createQuery('SELECT count(j.id) from IbwJobeetBundle:Job j WHERE j.position = :position AND j.is_activated = 1');\r\n    $query-&gt;setParameter('position', 'FOO1');\r\n    $this-&gt;assertTrue(0 &lt; $query-&gt;getSingleScalarResult());\r\n}<\/pre>\n<p>Testing the <code>Delete<\/code> action is quite similar:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function testDeleteJob()\r\n{\r\n    $client = $this-&gt;createJob(array('job[position]' =&gt; 'FOO2'));\r\n    $crawler = $client-&gt;getCrawler();\r\n    $form = $crawler-&gt;selectButton('Delete')-&gt;form();\r\n    $client-&gt;submit($form);\r\n\r\n    $kernel = static::createKernel();\r\n    $kernel-&gt;boot();\r\n    $em = $kernel-&gt;getContainer()-&gt;get('doctrine.orm.entity_manager');\r\n\r\n    $query = $em-&gt;createQuery('SELECT count(j.id) from IbwJobeetBundle:Job j WHERE j.position = :position');\r\n    $query-&gt;setParameter('position', 'FOO2');\r\n    $this-&gt;assertTrue(0 == $query-&gt;getSingleScalarResult());\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Tests_as_a_SafeGuard\"><\/span>Tests as a SafeGuard<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When a job is published, you cannot edit it anymore. Even if the \u201cEdit\u201d link is not displayed anymore on the preview page, let\u2019s add some tests for this requirement.<\/p>\n<p>First, add another argument to the <code>createJob()<\/code> method to allow automatic publication of the job, and create a <code>getJobByPosition()<\/code> method that returns a job given its position value:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function createJob($values = array(), $publish = false)\r\n{\r\n    $client = static::createClient();\r\n    $crawler = $client-&gt;request('GET', '\/job\/new');\r\n    $form = $crawler-&gt;selectButton('Preview your job')-&gt;form(array_merge(array(\r\n        'job[company]'      =&gt; 'Sensio Labs',\r\n        'job[url]'          =&gt; 'http:\/\/www.sensio.com\/',\r\n        'job[position]'     =&gt; 'Developer',\r\n        'job[location]'     =&gt; 'Atlanta, USA',\r\n        'job[description]'  =&gt; 'You will work with symfony to develop websites for our customers.',\r\n        'job[how_to_apply]' =&gt; 'Send me an email',\r\n        'job[email]'        =&gt; 'for.a.job@example.com',\r\n        'job[is_public]'    =&gt; false,\r\n  ), $values));\r\n\r\n    $client-&gt;submit($form);\r\n    $client-&gt;followRedirect();\r\n\r\n    if($publish) {\r\n      $crawler = $client-&gt;getCrawler();\r\n      $form = $crawler-&gt;selectButton('Publish')-&gt;form();\r\n      $client-&gt;submit($form);\r\n      $client-&gt;followRedirect();\r\n    }\r\n\r\n  return $client;\r\n}\r\n\r\npublic function getJobByPosition($position)\r\n{\r\n    $kernel = static::createKernel();\r\n    $kernel-&gt;boot();\r\n    $em = $kernel-&gt;getContainer()-&gt;get('doctrine.orm.entity_manager');\r\n\r\n    $query = $em-&gt;createQuery('SELECT j from IbwJobeetBundle:Job j WHERE j.position = :position');\r\n    $query-&gt;setParameter('position', $position);\r\n    $query-&gt;setMaxResults(1);\r\n    return $query-&gt;getSingleResult();\r\n}<\/pre>\n<p>If a job is published, the edit page must return a 404 status code:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function testEditJob()\r\n{\r\n    $client = $this-&gt;createJob(array('job[position]' =&gt; 'FOO3'), true);\r\n    $crawler = $client-&gt;getCrawler();\r\n    $crawler = $client-&gt;request('GET', sprintf('\/job\/%s\/edit', $this-&gt;getJobByPosition('FOO3')-&gt;getToken()));\r\n    $this-&gt;assertTrue(404 === $client-&gt;getResponse()-&gt;getStatusCode());\r\n}<\/pre>\n<p>But if you run the tests, you won\u2019t have the expected result as we forgot to implement this security measure yesterday. Writing tests is also a great way to discover bugs, as you need to think about all edge cases.<\/p>\n<p>Fixing the bug is quite simple as we just need to forward to a 404 page if the job is activated:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function editAction($token)\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n\r\n    $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;findOneByToken($token);\r\n\r\n    if (!$entity) {\r\n        throw $this-&gt;createNotFoundException('Unable to find Job entity.');\r\n    }\r\n\r\n    if ($entity-&gt;getIsActivated()) {\r\n        throw $this-&gt;createNotFoundException('Job is activated and cannot be edited.');\r\n    }\r\n\r\n  \/\/ ...\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Back_to_the_Future_in_a_Test\"><\/span>Back to the Future in a Test<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When a job is expiring in less than five days, or if it is already expired, the user can extend the job validation for another 30 days from the current date.<\/p>\n<p>Testing this requirement in a browser is not easy as the expiration date is automatically set when the job is created to 30 days in the future. So, when getting the job page, the link to extend the job is not present. Sure, you can hack the expiration date in the database, or tweak the template to always display the link, but that\u2019s tedious and error prone. As you have already guessed, writing some tests will help us one more time.<\/p>\n<p>As always, we need to add a new route for the <code>extend<\/code> method first:<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing\/job.yml\"># ...\r\n\r\nibw_job_extend:\r\n    pattern:  \/{token}\/extend\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:extend\" }\r\n    requirements: { _method: post }<\/pre>\n<p>Then, replace the <code>Extend<\/code> link code in the <code>admin.html.twig<\/code> partial with the extend form:<\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/admin.html.twig\">&lt;!-- ... --&gt;\r\n\r\n{% if job.expiresSoon %}\r\n    &lt;form action=\"{{ path('ibw_job_extend', { 'token': job.token }) }}\" method=\"post\"&gt;\r\n        {{ form_widget(extend_form) }}\r\n        &lt;button type=\"submit\"&gt;Extend&lt;\/button&gt; for another 30 days\r\n    &lt;\/form&gt;\r\n{% endif %}\r\n\r\n&lt;!-- ... --&gt;<\/pre>\n<p>Then, create the <code>extend<\/code> action and the extend form:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function extendAction(Request $request, $token)\r\n{\r\n    $form = $this-&gt;createExtendForm($token);\r\n    $request = $this-&gt;getRequest();\r\n\r\n    $form-&gt;bind($request);\r\n\r\n    if($form-&gt;isValid()) {\r\n        $em=$this-&gt;getDoctrine()-&gt;getManager();\r\n        $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;findOneByToken($token);\r\n\r\n        if(!$entity){\r\n            throw $this-&gt;createNotFoundException('Unable to find Job entity.');\r\n        }\r\n\r\n        if(!$entity-&gt;extend()){\r\n            throw $this-&gt;createNodFoundException('Unable to extend the Job');\r\n        }\r\n\r\n        $em-&gt;persist($entity);\r\n        $em-&gt;flush();\r\n\r\n        $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('notice', sprintf('Your job validity has been extended until %s', $entity-&gt;getExpiresAt()-&gt;format('m\/d\/Y')));\r\n    }\r\n\r\n    return $this-&gt;redirect($this-&gt;generateUrl('ibw_job_preview', array(\r\n        'company' =&gt; $entity-&gt;getCompanySlug(),\r\n        'location' =&gt; $entity-&gt;getLocationSlug(),\r\n        'token' =&gt; $entity-&gt;getToken(),\r\n        'position' =&gt; $entity-&gt;getPositionSlug()\r\n    )));\r\n}\r\n\r\nprivate function createExtendForm($token)\r\n{\r\n    return $this-&gt;createFormBuilder(array('token' =&gt; $token))\r\n        -&gt;add('token', 'hidden')\r\n        -&gt;getForm();\r\n}<\/pre>\n<p>Also, add the <code>extend<\/code> form to the <code>preview<\/code> action:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function previewAction($token)\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n\r\n    $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;findOneByToken($token);\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($entity-&gt;getId());\r\n    $publishForm = $this-&gt;createPublishForm($entity-&gt;getToken());\r\n    $extendForm = $this-&gt;createExtendForm($entity-&gt;getToken());\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        'publish_form' =&gt; $publishForm-&gt;createView(),\r\n        'extend_form' =&gt; $extendForm-&gt;createView(),\r\n    ));\r\n}<\/pre>\n<p>As expected by the action, the <code>extend()<\/code> method of <code>Job<\/code> returns <code>true<\/code> if the job has been extended or <code>false<\/code> otherwise:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Entity\/Job.php\">\/\/ ...\r\n\r\npublic function extend()\r\n{\r\n    if (!$this-&gt;expiresSoon())\r\n    {\r\n        return false;\r\n    }\r\n\r\n    $this-&gt;expires_at = new DateTime(date('Y-m-d H:i:s', time() + 86400 * 30));\r\n\r\n    return true;\r\n}<\/pre>\n<p>Eventually, add a test scenario:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src.Ibw\/JobeetBundle\/Tests\/Controller\/JobControllerTest.php\">\/\/ ...\r\n\r\npublic function testExtendJob()\r\n{\r\n    \/\/ A job validity cannot be extended before the job expires soon\r\n    $client = $this-&gt;createJob(array('job[position]' =&gt; 'FOO4'), true);\r\n    $crawler = $client-&gt;getCrawler();\r\n    $this-&gt;assertTrue($crawler-&gt;filter('input[type=submit]:contains(\"Extend\")')-&gt;count() == 0);\r\n\r\n    \/\/ A job validity can be extended when the job expires soon\r\n\r\n    \/\/ Create a new FOO5 job\r\n    $client = $this-&gt;createJob(array('job[position]' =&gt; 'FOO5'), true);\r\n\r\n    \/\/ Get the job and change the expire date to today\r\n    $kernel = static::createKernel();\r\n    $kernel-&gt;boot();\r\n    $em = $kernel-&gt;getContainer()-&gt;get('doctrine.orm.entity_manager');\r\n    $job = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;findOneByPosition('FOO5');\r\n    $job-&gt;setExpiresAt(new DateTime());\r\n    $em-&gt;flush();\r\n\r\n    \/\/ Go to the preview page and extend the job\r\n    $crawler = $client-&gt;request('GET', sprintf('\/job\/%s\/%s\/%s\/%s', $job-&gt;getCompanySlug(), $job-&gt;getLocationSlug(), $job-&gt;getToken(), $job-&gt;getPositionSlug()));\r\n    $crawler = $client-&gt;getCrawler();\r\n    $form = $crawler-&gt;selectButton('Extend')-&gt;form();\r\n    $client-&gt;submit($form);\r\n\r\n    \/\/ Reload the job from db\r\n    $job = $this-&gt;getJobByPosition('FOO5');\r\n\r\n    \/\/ Check the expiration date\r\n    $this-&gt;assertTrue($job-&gt;getExpiresAt()-&gt;format('y\/m\/d') == date('y\/m\/d', time() + 86400 * 30));\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Maintenance_Tasks\"><\/span>Maintenance Tasks<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Even if symfony is a web framework, it comes with a command line tool. You have already used it to create the default directory structure of the application bundle and to generate various files for the model. Adding a new command is quite easy.<\/p>\n<p>When a user creates a job, he must activate it to put it online. But if not, the database will grow with stale jobs. Let\u2019s create a command that remove stale jobs from the database. This command will have to be run regularly in a cron job.<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Command\/JobeetCleanupCommand.php\">namespace IbwJobeetBundleCommand;\r\n\r\nuse SymfonyBundleFrameworkBundleCommandContainerAwareCommand;\r\nuse SymfonyComponentConsoleInputInputArgument;\r\nuse SymfonyComponentConsoleInputInputInterface;\r\nuse SymfonyComponentConsoleInputInputOption;\r\nuse SymfonyComponentConsoleOutputOutputInterface;\r\nuse IbwJobeetBundleEntityJob;\r\n\r\nclass JobeetCleanupCommand extends ContainerAwareCommand {\r\n\r\n  protected function configure()\r\n  {\r\n      $this\r\n          -&gt;setName('ibw:jobeet:cleanup')\r\n          -&gt;setDescription('Cleanup Jobeet database')\r\n          -&gt;addArgument('days', InputArgument::OPTIONAL, 'The email', 90)\r\n    ;\r\n  }\r\n\r\n  protected function execute(InputInterface $input, OutputInterface $output)\r\n  {\r\n      $days = $input-&gt;getArgument('days');\r\n\r\n      $em = $this-&gt;getContainer()-&gt;get('doctrine')-&gt;getManager();\r\n      $nb = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;cleanup($days);\r\n\r\n      $output-&gt;writeln(sprintf('Removed %d stale jobs', $nb));\r\n  }\r\n}<\/pre>\n<p>You will have to add the <code>cleanup<\/code> method to the <code>JobRepository<\/code> class:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Repository\/JobRepository.php\">\/\/ ...\r\n\r\npublic function cleanup($days)\r\n{\r\n    $query = $this-&gt;createQueryBuilder('j')\r\n        -&gt;delete()\r\n        -&gt;where('j.is_activated IS NULL')\r\n        -&gt;andWhere('j.created_at &lt; :created_at')    \r\n        -&gt;setParameter('created_at',  date('Y-m-d', time() - 86400 * $days))\r\n        -&gt;getQuery();\r\n\r\n    return $query-&gt;execute();\r\n}<\/pre>\n<p>To run the command execute the following from the project folder:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">php app\/console ibw:jobeet:cleanup<\/pre>\n<p>or:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">php app\/console ibw:jobeet:cleanup 10<\/pre>\n<p>to delete stale jobs older than 10 days.<br \/>\n<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. Testing your forms [&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":[6,528,530,1355,1369,1376,1395,1948],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2250"}],"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=2250"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2250\/revisions"}],"predecessor-version":[{"id":133234,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2250\/revisions\/133234"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2250"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2250"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2250"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2250"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}