The Architecture ================ You are my hero! Who would have thought that you would still be here after the first two parts? Your efforts will be well-rewarded soon. The first two parts didn't look too deeply at the architecture of the framework. Because it makes Symfony stand apart from the framework crowd, let's dive into the architecture now. Add Logging ----------- A new Symfony app is micro: it's basically just a routing & controller system. But thanks to Flex, installing more features is simple. Want a logging system? No problem: .. code-block:: terminal $ composer require logger This installs and configures (via a recipe) the powerful `Monolog`_ library. To use the logger in a controller, add a new argument type-hinted with ``LoggerInterface``:: // src/Controller/DefaultController.php namespace App\Controller; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class DefaultController extends AbstractController { /** * @Route("/hello/{name}") */ public function index($name, LoggerInterface $logger) { $logger->info("Saying hello to $name!"); // ... } } That's it! The new log message will be written to ``var/log/dev.log``. The log file path or even a different method of logging can be configured by updating one of the config files added by the recipe. Services & Autowiring --------------------- But wait! Something *very* cool just happened. Symfony read the ``LoggerInterface`` type-hint and automatically figured out that it should pass us the Logger object! This is called *autowiring*. Every bit of work that's done in a Symfony app is done by an *object*: the Logger object logs things and the Twig object renders templates. These objects are called *services* and they are *tools* that help you build rich features. To make life awesome, you can ask Symfony to pass you a service by using a type-hint. What other possible classes or interfaces could you use? Find out by running: .. code-block:: terminal $ php bin/console debug:autowiring # this is just a *small* sample of the output... Describes a logger instance. Psr\Log\LoggerInterface (monolog.logger) Request stack that controls the lifecycle of requests. Symfony\Component\HttpFoundation\RequestStack (request_stack) Interface for the session. Symfony\Component\HttpFoundation\Session\SessionInterface (session) RouterInterface is the interface that all Router classes must implement. Symfony\Component\Routing\RouterInterface (router.default) [...] This is just a short summary of the full list! And as you add more packages, this list of tools will grow! Creating Services ----------------- To keep your code organized, you can even create your own services! Suppose you want to generate a random greeting (e.g. "Hello", "Yo", etc). Instead of putting this code directly in your controller, create a new class:: // src/GreetingGenerator.php namespace App; class GreetingGenerator { public function getRandomGreeting() { $greetings = ['Hey', 'Yo', 'Aloha']; $greeting = $greetings[array_rand($greetings)]; return $greeting; } } Great! You can use this immediately in your controller:: // src/Controller/DefaultController.php namespace App\Controller; use App\GreetingGenerator; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class DefaultController extends AbstractController { /** * @Route("/hello/{name}") */ public function index($name, LoggerInterface $logger, GreetingGenerator $generator) { $greeting = $generator->getRandomGreeting(); $logger->info("Saying $greeting to $name!"); // ... } } That's it! Symfony will instantiate the ``GreetingGenerator`` automatically and pass it as an argument. But, could we *also* move the logger logic to ``GreetingGenerator``? Yes! You can use autowiring inside a service to access *other* services. The only difference is that it's done in the constructor: .. code-block:: diff // src/GreetingGenerator.php + use Psr\Log\LoggerInterface; class GreetingGenerator { + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } public function getRandomGreeting() { // ... + $this->logger->info('Using the greeting: '.$greeting); return $greeting; } } Yes! This works too: no configuration, no time wasted. Keep coding! Twig Extension & Autoconfiguration ---------------------------------- Thanks to Symfony's service handling, you can *extend* Symfony in many ways, like by creating an event subscriber or a security voter for complex authorization rules. Let's add a new filter to Twig called ``greet``. How? Create a class that extends ``AbstractExtension``:: // src/Twig/GreetExtension.php namespace App\Twig; use App\GreetingGenerator; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; class GreetExtension extends AbstractExtension { private $greetingGenerator; public function __construct(GreetingGenerator $greetingGenerator) { $this->greetingGenerator = $greetingGenerator; } public function getFilters() { return [ new TwigFilter('greet', [$this, 'greetUser']), ]; } public function greetUser($name) { $greeting = $this->greetingGenerator->getRandomGreeting(); return "$greeting $name!"; } } After creating just *one* file, you can use this immediately: .. code-block:: html+twig {# templates/default/index.html.twig #} {# Will print something like "Hey Symfony!" #}