Routes, Controllers, Pages, oh my!
Let's create our first page! Actually, this is the main job of a framework: to give you a route and controller system. A route is configuration that defines the URL for a page and a controller is a function that we write that actually builds the content for that page.
And right now... our app is really small! Instead of weighing down your project with every possible feature you could ever need - after all, we're not in zero-gravity yet - a Symfony app is basically just a small route-controller system. Later, we'll install more features when we need them, like a warp drive! Those always come in handy. Adding more features is actually going to be pretty awesome. More on that later.
First Route & Controller
Open your app's main routing file: config/routes.yaml
:
#index: | |
# path: / | |
# controller: App\Controller\DefaultController::index |
Hey! We already have an example! Uncomment that. Ignore the index
key for now: that's the internal name of the route, but it's not important yet.
This says that when someone goes to the homepage - /
- Symfony should execute an index()
method in a DefaultController
class. Change this to ArticleController
and the method to homepage
:
index: | |
path: / | |
controller: App\Controller\ArticleController::homepage |
And... yea! That's a route! Hi route! It defines the URL and tells Symfony what controller function to execute.
The controller class doesn't exist yet, so let's create it! Right-click on the Controller
directory and go to "New" or press Cmd
+N
on a Mac. Choose "PHP Class". And, yes! Remember that Composer setup we did in Preferences? Thanks to that, PhpStorm correctly guesses the namespace! The force is strong with this one... The namespace for every class in src/
should be App
plus whatever sub-directory it's in.
Name this ArticleController
:
namespace App\Controller; | |
// ... lines 4 - 6 | |
class ArticleController | |
{ | |
// ... lines 9 - 12 | |
} |
And inside, add public function homepage()
:
// ... lines 1 - 2 | |
namespace App\Controller; | |
// ... lines 4 - 6 | |
class ArticleController | |
{ | |
public function homepage() | |
{ | |
// ... line 11 | |
} | |
} |
This function is the controller... and it's our place to build the page. To be more confusing, it's also called an "action", or "ghob" to its Klingon friends.
Anyways, we can do whatever we want here: make database queries, API calls, take soil samples looking for organic materials or render a template. There's just one rule: a controller must return a Symfony Response
object.
So let's say: return new Response()
: we want the one from HttpFoundation
. Give it a calm message: OMG!
My first page already! WOOO!:
// ... lines 1 - 2 | |
namespace App\Controller; | |
use Symfony\Component\HttpFoundation\Response; | |
class ArticleController | |
{ | |
public function homepage() | |
{ | |
return new Response('OMG! My first page already! WOOO!'); | |
} | |
} |
Ahem. Oh, and check this out: when I let PhpStorm auto-complete the Response
class it added this use
statement to the top of the file automatically:
// ... lines 1 - 4 | |
use Symfony\Component\HttpFoundation\Response; | |
// ... lines 6 - 14 |
You'll see me do that a lot. Good job Storm!
Let's try the page! Find your browser. Oh, this "Welcome" page only shows if you don't have any routes configured. Refresh! Yes! This is our page. Our first of many.
Annotation Routes
That was pretty easy, but it can be easier! Instead of creating our routes in YAML, let's use a cool feature called annotations. This is an extra feature, so we need to install it. Find your open terminal and run:
composer require annotations
Interesting... this annotations
package actually installed sensio/framework-extra-bundle
. We're going to talk about how that works very soon.
Now, about these annotation routes. Comment-out the YAML route:
#index: | |
# path: / | |
# controller: App\Controller\ArticleController::homepage |
Then, in ArticleController
, above the controller method, add /**
, hit enter, clear this out, and say @Route()
. You can use either class - but make sure PhpStorm adds the use
statement on top. Then add "/"
:
// ... lines 1 - 4 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | |
// ... lines 6 - 7 | |
class ArticleController | |
{ | |
/** | |
* @Route("/") | |
*/ | |
public function homepage() | |
{ | |
// ... line 15 | |
} | |
} |
Tip
When you auto-complete the @Route
annotation, be sure to choose the
one from Symfony\Component\Routing
- the one we chose is now deprecated.
Both work the same.
That's it! The route is defined right above the controller, which is why I love annotation routes: everything is in one place. But don't trust me, find your browser and refresh. It's a traaaap! I mean, it works!
Tip
What exactly are annotations? They're PHP comments that are read as configuration.
Fancy Wildcard Routes
So what else can we do with routes? Create another public function called show()
. I want this page to eventually display a full article. Give it a route: @Route("/news/why-asteroids-taste-like-bacon")
:
// ... lines 1 - 4 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | |
// ... lines 6 - 7 | |
class ArticleController | |
{ | |
// ... lines 10 - 17 | |
/** | |
* @Route("/news/why-asteroids-taste-like-bacon") | |
*/ | |
public function show() | |
{ | |
// ... line 23 | |
} | |
} |
Eventually, this is how we want our URLs to look. This is called a "slug", it's a URL version of the title. As usual, return a new Response('Future page to show one space article!')
:
// ... lines 1 - 4 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | |
use Symfony\Component\HttpFoundation\Response; | |
class ArticleController | |
{ | |
// ... lines 10 - 17 | |
/** | |
* @Route("/news/why-asteroids-taste-like-bacon") | |
*/ | |
public function show() | |
{ | |
return new Response('Future page to show one space article!'); | |
} | |
} |
Perfect! Copy that URL and try it in your browser. It works... but this sucks! I don't want to build a route and controller for every single article that lives in the database. Nope, we need a route that can match /news/
anything. How? Use {slug}
:
// ... lines 1 - 7 | |
class ArticleController | |
{ | |
// ... lines 10 - 17 | |
/** | |
* @Route("/news/{slug}") | |
*/ | |
public function show($slug) | |
{ | |
// ... lines 23 - 26 | |
} | |
} |
This route now matches /news/
anything: that {slug}
is a wildcard. Oh, and the name slug
could be anything. But whatever you choose now becomes available as an argument to your "ghob", I mean your action.
So let's refactor our success message to say:
Future page to show the article
And then that slug:
// ... lines 1 - 7 | |
class ArticleController | |
{ | |
// ... lines 10 - 17 | |
/** | |
* @Route("/news/{slug}") | |
*/ | |
public function show($slug) | |
{ | |
return new Response(sprintf( | |
'Future page to show the article: "%s"', | |
$slug | |
)); | |
} | |
} |
Try it! Refresh the same URL. Yes! It matches the route and the slug prints! Change it to something else: /why-asteroids-taste-like-tacos
. So delicious! Go back to bacon... because... ya know... everyone knows that's what asteroids really taste like.
And... yes! We're 3 chapters in and you now know the first half of Symfony: the route & controller system. Sure, you can do fancier things with routes, like match regular expressions, HTTP methods or host names - but that will all be pretty easy for you now.
It's time to move on to something really important: it's time to learn about Symfony Flex and the recipe system. Yum!
I'm at "omg my first page already" and I'm following along/started the project by myself, and I have this error loading index.php:
Parse error: syntax error, unexpected '?' in C:\www\eds-www\tuts\the_spacebar\public\index.php on line 15
Here's line 15-17 of index.php:
`if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
}`
What have I fouled up?