gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
The homepage will eventually be the place where a user can design and build their next sweet mix tape. But in addition to creating new tapes, users will also be able to browse other people's creations.
Let's make a second page for that. How? By adding a second controller: public function, how about browse
: the name doesn't really matter. And to be responsible, I'll add a Response
return type.
Above this, we need our route. This will look exactly the same, except set the URL to /browse
. Inside the method, what do we always return from a controller? That's right: a Response
object! Return a new Response
... with a short message to start.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse') ( | |
public function browse(): Response | |
{ | |
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!'); | |
} | |
} |
Let's try it! If we refresh the homepage, nothing changes. But if we go to /browse
... we're crushing it! A second page in under a minute! Dang!
On this page, we'll eventually list mix tapes from other users. To help find something we like, I want users to also be able to browse by genre. For example, if I go to /browse/death-metal
, that would show me all the death metal vinyl mix tapes. Hardcore.
Of course, if we try this URL right now... it doesn't work.
Not Route found
No matching routes were found for this URL, so it shows us a 404 page. By the way, what you're seeing is Symfony's fancy exception page, because we're currently developing. It gives us tons of details whenever something goes wrong. When you eventually deploy to production, you can design a different error page that your users would see.
Anyways, the simplest way to make this URL work is just... to change the URL to /browse/death-metal
.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse/death-metal') ( | |
public function browse(): Response | |
{ | |
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!'); | |
} | |
} |
But... not super flexible, right? We would need one route for every genre... which could be hundreds! And also, we just killed the /browse
URL! It now 404's.
What we really want is a route that match /browse/<ANYTHING>
. And we can do that with a wildcard. Replace the hard-coded death-metal
with {}
and, inside, slug
. Slug is just a technical word for a "URL-safe name". Really, we could have put anything inside the curly-braces, like {genre}
or {coolMusicCategory}
: it makes no difference. But whatever we put inside this wildcard, we are then allowed to have an argument with that same name: $slug
.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse/{slug}') ( | |
public function browse(): Response | |
{ | |
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!'); | |
} | |
} |
Yup, if we go to /browse/death-metal
, it will match this route and pass the string death-metal
to that argument. The matching is done by name: {slug}
connects to $slug
.
To see if it's working, let's return a different response: Genre
then the $slug
.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse/{slug}') ( | |
public function browse($slug): Response | |
{ | |
return new Response('Genre: '.$slug); | |
//return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!'); | |
} | |
... lines 23 - 24 |
Testing time! Head back to /browse/death-metal
and... yes! Try /browse/emo
and yea! I'm that much closer to my Dashboard Confessional mix tape!
Oh, and it's optional, but you can add a string
type to the $slug
argument. That doesn't change anything... it's just a nice way to program: the $slug
was already always going to be a string.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse/{slug}') ( | |
public function browse(string $slug): Response | |
{ | |
... lines 19 - 21 | |
} | |
... lines 23 - 24 |
A bit later, we'll learn how you could turn a number wildcard - like the number 5 - into an integer if you want to.
Let's make this page a bit fancier. Instead of printing out the slug exactly, let's convert it to a title. Say $title = str_replace()
and replace any dashes with spaces. Then, down here, use title in the response. In a future tutorial, we're going to query the database for these genres, but, for right now, we can at least make it look nicer.
... lines 1 - 7 | |
class VinylController | |
{ | |
... lines 10 - 15 | |
'/browse/{slug}') ( | |
public function browse(string $slug): Response | |
{ | |
$title = str_replace('-', ' ', $slug); | |
return new Response('Genre: '.$title); | |
... line 23 | |
} | |
... lines 25 - 26 |
If we try it, Emo doesn't look any different... but death metal does. But I want more fancy! Add another line with $title =
then type u
and auto-complete a function that's literally called... u
.
We don't use many functions from Symfony, but this is a rare example. This comes from a Symfony library called symfony/string
. As I mentioned, Symfony is many different libraries - also called components - and we're going to leverage those libraries all the time. This one helps you make string transformations... and it happens to already be installed.
Move the str_replace()
to the first argument of u()
. This function returns an object that we can then do string operations on. One method is called title()
. Say ->title(true)
to convert all words to title case.
... lines 1 - 6 | |
use function Symfony\Component\String\u; | |
... line 8 | |
class VinylController | |
{ | |
... lines 11 - 15 | |
'/browse/{slug}') ( | |
public function browse(string $slug): Response | |
{ | |
$title = u(str_replace('-', ' ', $slug))->title(true); | |
return new Response('Genre: '.$title); | |
... lines 23 - 24 | |
} | |
... lines 26 - 27 |
Now whe n we try it... sweet! It uppercases the letters! The string component isn't particularly important, I just want you to see how we can already leverage parts of Symfony to get our job done.
Ok: one last challenge. Going to /browse/emo
or /browse/death-metal
works. But just going to /browse
... does not work. It's broken! A wild card can match anything, but, by default, a wild card is required. We have to go to /browse/<something>
.
Can we make a wildcard optional? Absolutely! And it's delightfully simple: make the corresponding argument optional.
... lines 1 - 8 | |
class VinylController | |
{ | |
... lines 11 - 15 | |
'/browse/{slug}') ( | |
public function browse(string $slug = null): Response | |
{ | |
... lines 20 - 24 | |
} | |
... lines 26 - 27 |
As soon as we do that, it tells Symfony's routing layer that the {slug}
does not need to be in the URL. So now when we refresh... it works. Though, that's not a great message for the page.
Let's see. If there's a slug, then set the title the way we were. Else, set $title
to "All genres". Oh, and move the "Genre:" up here... so that down in the Response
we can just pass $title
.
... lines 1 - 8 | |
class VinylController | |
{ | |
... lines 11 - 15 | |
'/browse/{slug}') ( | |
public function browse(string $slug = null): Response | |
{ | |
if ($slug) { | |
$title = 'Genre: '.u(str_replace('-', ' ', $slug))->title(true); | |
} else { | |
$title = 'All Genres'; | |
} | |
return new Response($title); | |
... lines 27 - 28 | |
} | |
... lines 30 - 31 |
Try that. On /browse
... "All Genres". On /browse/emo
... "Genre: Emo".
Next: putting text like this into a controller.... isn't very clean or scalable, especially if we start including HTML. Nope, we need to render a template. To do that, we're going to install our first third-party package and witness the massively important Symfony recipe system in action.
Hey boban_dj
The fuction import is not shown in the video but if you look on the code blocks from the script, you'll see Ryan used exactly that function. Good job finding it out.
Cheers!
i really want to say i appreciate these videos ...for someone who is new on symfony your way of doing things makes super sense
Hey @Mazzucato ,
Both ways work actually. If you would read the last sentence in that explanation:
And even though {highlight} is in the route, you do not need to have an argument for that!
we mean that you don't have to give every wildcard a corresponding argument in the method signature. You can skip that $highlight
at all as in our example. E.g. it may just exist there but you don't need it in the method's code, i.e. don't use it there. That's what we meant there :)
Cheers!
i really want to say i appreciate these videos ...for someone who is new on symfony your way of doing things makes super sense sure
Hi!
My application has to manage multiple domains. When I'm using the 'host' route parameter, it matches differently if user entered 'www' or not in the browser: www.example.com and example.com are not considered as the same host. How can I add a wildcard that could solve that?
Thanks!
Hey Sebastien D.
I think a better solution would be to add a redirect at the web server level, for example all requests coming from www.example.com should redirect to example.com, or viceversa.
Cheers!
Hi, I am working on Ubuntu 20.04 and installed apache2,php, mysql and composer. On symfony 6 , #[Route('/browse')] doesn't work. I have 404 page "The requested URL was not found on this server." I have installed "symfony/apache-pack". I still have the same result.
`#[Route('/browse')]
public function browse(): Response
{
return new Response('Browse page');
}`
Best regards
Hey Eskinder,
First of all, please, make sure the route is exist in the system - for this, run:
$ bin/console debug:router | grep browse
Do you see the route in the output? is it just "/browse" or does it has a prefix? Also, make sure this route is registered for GET (or any) requests. If so, then most probably the problem in your Apache config. First of all, make sure you set the public/ directory as your document root. Then, double-check the other config. Actually, Symfony gives the correct Apache/Nginx configs in the docs, see: https://symfony.com/doc/cur... - there's important to know if you configure it via PHP-FPM or in a different way - there are different configs. But basically, if the route exist and you configured the host well using the config from the docs - it should work. Also, don't forget to restart your Apache server after tweaking any config - that's important, most users forget to do this :)
I hope this helps!
Cheers!
Hi,
I have a strange question, I hope you will understand me.
In your example, "/browse" is hard coded.
Is it possible to save "/ browse" in the database and call from there.
Problem is if user wants to change "/browse" to "/somethig-else" he can't do that from cms it must be changed in code.
Best regards
Yes, that is why you have the {wildcard}
route. So all you need to do is change the /browse
route to /{browse}
now it matches anything you pass to it and you can check if that route or slug exists in your database
// composer.json
{
"require": {
"php": ">=8.0.2",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/asset": "6.0.*", // v6.0.3
"symfony/console": "6.0.*", // v6.0.3
"symfony/dotenv": "6.0.*", // v6.0.3
"symfony/flex": "^2", // v2.1.5
"symfony/framework-bundle": "6.0.*", // v6.0.4
"symfony/monolog-bundle": "^3.0", // v3.7.1
"symfony/runtime": "6.0.*", // v6.0.3
"symfony/twig-bundle": "6.0.*", // v6.0.3
"symfony/ux-turbo": "^2.0", // v2.0.1
"symfony/webpack-encore-bundle": "^1.13", // v1.13.2
"symfony/yaml": "6.0.*", // v6.0.3
"twig/extra-bundle": "^2.12|^3.0", // v3.3.8
"twig/twig": "^2.12|^3.0" // v3.3.8
},
"require-dev": {
"symfony/debug-bundle": "6.0.*", // v6.0.3
"symfony/stopwatch": "6.0.*", // v6.0.3
"symfony/web-profiler-bundle": "6.0.*" // v6.0.3
}
}
I have to add: use function Symfony\Component\String\u; to make it work. VSCodium does not show autocomplete, but here is well explained too https://symfony.com/doc/cur...