{"id":2199,"date":"2013-08-11T07:36:31","date_gmt":"2013-08-11T07:36:31","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2199"},"modified":"2024-05-31T09:06:15","modified_gmt":"2024-05-31T09:06:15","slug":"symfony2-jobeet-day-5-the-routing","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-5-the-routing\/","title":{"rendered":"Symfony2 Jobeet Day 5: The Routing"},"content":{"rendered":"<p><span style=\"font-family: timesnew roman; font-size: 12px;\">* This article is part of the original <a href=\"\/\/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<h2>URLs<\/h2>\n<p>If you click on a job on the Jobeet homepage, the URL looks like this: <code>\/job\/1\/show<\/code>. If you have already developed PHP websites, you are probably more accustomed to URLs like <code>\/job.php?id=1<\/code>. How does Symfony make it work? How does Symfony determine the action to call based on this URL? Why is the id of the job retrieved with the <code>$id<\/code> parameter in the action? Here, we will answer all these questions.<span id=\"more-183\"><\/span><\/p>\n<p>You have already seen the following code in the <code>src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/index.html.twig<\/code> template:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">{{ path('ibw_job_show', { 'id': entity.id }) }}<\/pre>\n<p>This uses the <code>path<\/code> template helper function to generate the url for the job which has the <code>id 1<\/code>. The <code>ibw_job_show<\/code> is the name of the route used, defined in the configuration as you will see below.<\/p>\n<h3>Routing Configuration<\/h3>\n<p>In Symfony2, routing configuration is usually done in the <code>app\/config\/routing.yml<\/code>. This imports specific bundle routing configuration. In our case, the <code>src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml<\/code> file is imported:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"app\/config\/routing.yml\">ibw_jobeet:\r\n    resource: \"@IbwJobeetBundle\/Resources\/config\/routing.yml\"\r\n    prefix:   \/<\/pre>\n<p>Now, if you look in the JobeetBundle <code>routing.yml<\/code> you will see that it imports another routing file, the one for the Job controller and defines a route called <code>ibw_jobeet_homepage <\/code>for the <code>\/hello\/{name}<\/code> URL pattern:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml\">IbwJobeetBundle_job:\r\n    resource: \"@IbwJobeetBundle\/Resources\/config\/routing\/job.yml\"\r\n    prefix: \/job\r\n\r\nibw_jobeet_homepage:\r\n    pattern:  \/hello\/{name}\r\n    defaults: { _controller: IbwJobeetBundle:Default:index }<\/pre>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing\/job.yml\">ibw_job:\r\n    pattern:  \/\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:index\" }\r\n\r\nibw_job_show:\r\n    pattern:  \/{id}\/show\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:show\" }\r\n\r\nibw_job_new:\r\n    pattern:  \/new\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:new\" }\r\n\r\nibw_job_create:\r\n    pattern:  \/create\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:create\" }\r\n    requirements: { _method: post }\r\n\r\nibw_job_edit:\r\n    pattern:  \/{id}\/edit\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:edit\" }\r\n\r\nibw_job_update:\r\n    pattern:  \/{id}\/update\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:update\" }\r\n    requirements: { _method: post|put }\r\n\r\nibw_job_delete:\r\n    pattern:  \/{id}\/delete\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:delete\" }\r\n    requirements: { _method: post|delete }<\/pre>\n<p>Let\u2019s have a closer look to the <code>ibw_job_show<\/code> route. The pattern defined by the <code>ibw_job_show <\/code>route acts like <code>\/*\/show<\/code> where the wildcard is given the name <code>id<\/code>. For the URL <code>\/1\/show<\/code>, the <code>id <\/code>variable gets a value of 1, which is available for you to use in your controller. The <code>_controller <\/code>parameter is a special key that tells Symfony which controller\/action should be executed when a URL matches this route, in our case it should execute the <code>showAction<\/code> from the <code>JobController<\/code> in the <code>IbwJobeetBundle<\/code>.<\/p>\n<p>The route parameters (e.g. <code>{id}<\/code>) are especially important because each is made available as an argument to the controller method.<\/p>\n<h3>Routing Configuration in Dev Environment<\/h3>\n<p>The dev environment loads the <code>app\/config\/routing_dev.yml<\/code> file that contains the routes used by the <strong>Web Debug Toolbar<\/strong> (you already deleted the routes for the <code>AcmeDemoBundle<\/code> from <code>\/app\/config\/routing_dev.php<\/code> \u2013 see Day 1, <strong>How to remove the AcmeDemoBundle<\/strong>). This file loads, at the end, the main <code>routing.yml<\/code> configuration file.<\/p>\n<h3>Route Customizations<\/h3>\n<p>For now, when you request the \/ URL in a browser, you will get a 404 Not Found error. That\u2019s because this URL does not match any routes defined. We have a <code>ibw_jobeet_homepage<\/code> route that matches the <code>\/hello\/jobeet<\/code> URL and sends us to the <code>DefaultController<\/code>, <code>index<\/code> action. Let\u2019s change it to match the <code>\/<\/code> URL and to call the index action from the <code>JobController<\/code>. To make the change, modify it to the following:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml\"># ...\r\nibw_jobeet_homepage:\r\n    pattern:  \/\r\n    defaults: { _controller: IbwJobeetBundle:Job:index }<\/pre>\n<p>Now, if you clear the cache and go to http:\/\/jobeet.local from your browser, you will see the <code>Job<\/code> homepage. We can now change the link of the Jobeet logo in the layout to use the <code>ibw_jobeet_homepage<\/code> route:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true\" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/layout.html.twig\">&lt;!-- ... --&gt;    \r\n    &lt;h1&gt;&lt;a href=\"{{ path('ibw_jobeet_homepage') }}\"&gt;\r\n        &lt;img alt=\"Jobeet Job Board\" src=\"{{ asset('bundles\/ibwjobeet\/images\/logo.jpg') }}\" \/&gt;\r\n    &lt;\/a&gt;&lt;\/h1&gt;\r\n&lt;!-- ... --&gt;<\/pre>\n<p>For something a bit more involved, let\u2019s change the job page URL to something more meaningful:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">\/job\/sensio-labs\/paris-france\/1\/web-developer<\/pre>\n<p>Without knowing anything about Jobeet, and without looking at the page, you can understand from the URL that Sensio Labs is looking for a Web developer to work in Paris, France.<\/p>\n<p>The following pattern matches such a URL:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">\/job\/{company}\/{location}\/{id}\/{position}<\/pre>\n<p>Edit the <code>ibw_job_show<\/code> route from the <code>job.yml<\/code> file:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing\/job.yml\"># ...\r\n\r\nibw_job_show:\r\n    pattern:  \/{company}\/{location}\/{id}\/{position}\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:show\" }<\/pre>\n<p>Now, we need to pass all the parameters for the changed route for it to work:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Job\/index.html.twig\">&lt;!-- ... --&gt;\r\n&lt;a href=\"{{ path('ibw_job_show', { 'id': entity.id, 'company': entity.company, 'location': entity.location, 'position': entity.position }) }}\"&gt;\r\n    {{ entity.position }}\r\n&lt;\/a&gt;\r\n&lt;!-- ... --&gt;<\/pre>\n<p>If you have a look at generated URLs, they are not quite yet as we want them to be:<\/p>\n<div>\n<div id=\"highlighter_699475\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td><code><code>http:\/\/jobeet.local\/app_dev.php\/job\/Sensio Labs\/Paris,France\/1\/Web Developer<\/code><\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<div>\n<div id=\"highlighter_699475\"><span style=\"line-height: 1.714285714; font-size: 1rem;\">We need to \u201cslugify\u201d the column values by replacing all non ASCII characters by a -. Open the <\/span><code style=\"line-height: 1.714285714;\">Job.php<\/code><span style=\"line-height: 1.714285714; font-size: 1rem;\"> file and add the following methods to the class:<\/span><\/div>\n<div>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Entity\/Job.php\">\/\/ ...\r\nuse IbwJobeetBundleUtilsJobeet as Jobeet;\r\n\r\nclass Job\r\n{\r\n    \/\/ ...\r\n\r\n    public function getCompanySlug()\r\n    {\r\n        return Jobeet::slugify($this-&gt;getCompany());\r\n    }\r\n\r\n    public function getPositionSlug()\r\n    {\r\n        return Jobeet::slugify($this-&gt;getPosition());\r\n    }\r\n\r\n    public function getLocationSlug()\r\n    {\r\n        return Jobeet::slugify($this-&gt;getLocation());\r\n    }\r\n}<\/pre>\n<p>You must also add the <code>use<\/code> statement before the <code>Job<\/code> class definition.<br \/>\nAfter that, create the <code>src\/Ibw\/JobeetBundle\/Utils\/Jobeet.php<\/code> file and add the <code>slugify<\/code> method in it:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Utils\/Jobeet.php\">namespace IbwJobeetBundleUtils;\r\n\r\nclass Jobeet\r\n{\r\n    static public function slugify($text)\r\n    {\r\n        \/\/ replace all non letters or digits by -\r\n        $text = preg_replace('\/W+\/', '-', $text);\r\n\r\n        \/\/ trim and lowercase\r\n        $text = strtolower(trim($text, '-'));\r\n\r\n        return $text;\r\n    }\r\n}<\/pre>\n<p>We have defined three new \u201cvirtual\u201d accessors: <code>getCompanySlug()<\/code>, <code>getPositionSlug()<\/code>, and <code>getLocationSlug()<\/code>. They return their corresponding column value after applying it the <code>slugify()<\/code> method. Now, you can replace the real column names by these virtual ones in the template:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/views\/Job\/index.html.twig\">&lt;!-- ... --&gt;\r\n&lt;a href=\"{{ path('ibw_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug}) }}\"&gt;\r\n   {{ entity.position }}\r\n&lt;\/a&gt;\r\n&lt;!-- ... --&gt;<\/pre>\n<h3>Route Requirements<\/h3>\n<p>The routing system has a built-in validation feature. Each pattern variable can be validated by a regular expression defined using the requirements entry of a route definition:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:1 lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing\/job.yml\"># ...\r\nibw_job_show:\r\n    pattern:  \/{company}\/{location}\/{id}\/{position}\r\n    defaults: { _controller: \"IbwJobeetBundle:Job:show\" }\r\n    requirements:\r\n        id:  d+\r\n\r\n# ...<\/pre>\n<p>The above requirements entry forces the id to be a numeric value. If not, the route won\u2019t match.<\/p>\n<h3>Route Debugging<\/h3>\n<p>While adding and customizing routes, it\u2019s helpful to be able to visualize and get detailed information about your routes. A great way to see every route in your application is via the <code>router:debug<\/code> console command. Execute the command by running the following from the root of your project:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">php app\/console router:debug<\/pre>\n<p>The command will print a helpful list of all the configured routes in your application. You can also get very specific information on a single route by including the route name after the command:<\/p>\n<pre class=\"theme:tomorrow-night toolbar:2 nums:false lang:default decode:true \">php app\/console router:debug ibw_job_show<\/pre>\n<h3>Final Thoughts<\/h3>\n<p>That\u2019s all for today! To learn more about the Symfony2 routing system read the Routing chapter form the book.<\/p>\n<\/div>\n<\/div>\n<p><a href=\"\/\/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=\"\/\/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. URLs If you [&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":[275,422,564,1013,1038,1236,1355,1948],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2199"}],"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=2199"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2199\/revisions"}],"predecessor-version":[{"id":133054,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2199\/revisions\/133054"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2199"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2199"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2199"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2199"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}