Intro to Services

Ok! The first half of Symfony: route-controller-response is in the books!

The second half is all about useful objects. Obviously, returning a string response like this is not going to take us very far. Our aquanauts demand more! In real life, we might need to render a template, query a database, or even turn objects into JSON for an API!

Symfony comes with many, optional, useful objects to help out with stuff like this. For example, want to send an email? Symfony has an object for that. How about log something? There's an object for that.

These objects are commonly called services, and that's important: when you hear the word service, just think "useful object".

Service Container

To keep track of all of these services, Symfony puts them into one big associative array called the container. Each object has a key - like mailer or logger. And to be more honest with you - sorry, I do like to lie temporarily - the container is actually an object. But think of it like an array: each useful object has an associated key. If I give you the container, you can ask for the logger service and it'll give you that object.

The second half of Symfony is all about finding out what objects are available and how to use them. Heck, we'll even add our own service objects to the container before too long. That's when things get really cool.

Accessing the Container

The first useful object is the templating service: it renders Twig templates. To get access to the service container, you need to extend Symfony's base controller.

Go Deeper!

Why does extending Controller give you access to the container? Find out: Injecting the Container: ContainerAwareInterface (advanced).

In GenusController, add extends Controller from FrameworkBundle. Hit tab to autocomplete and get the use statement:

24 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 5
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
... lines 7 - 8
class GenusController extends Controller
{
... lines 11 - 22
}

To get the templating service, add $templating = $this->container->get('templating'):

24 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 8
class GenusController extends Controller
{
... lines 11 - 13
public function showAction($genusName)
{
$templating = $this->container->get('templating');
... lines 17 - 21
}
}

The container pretty much only has one method: get. Give it the nickname to the service and it will return that object. It's super simple.

Quickly, open the var directory, right click on the cache directory and click "mark this directory as excluded". Symfony caches things... that's not important yet, but excluding this is important: this directory confuses autocompletion.

Now type $this->container->get('templating'). Well hey autocompletion!

Rendering a Template

With the templating object we can... well... render a template! Add $html = $templating->render('') followed by the name of the template. This could be anything, but let's be logical: genus/show.html.twig. I'll show you where this lives in a second:

24 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 8
class GenusController extends Controller
{
... lines 11 - 13
public function showAction($genusName)
{
$templating = $this->container->get('templating');
$html = $templating->render('genus/show.html.twig', array(
'name' => $genusName
));
... lines 20 - 21
}
}

We'll also want to pass some variables into the template. Pass a name variable into Twig that's set to $genusName.

Finally, what do we always do in Symfony controllers? We always return a Symfony's Response object. Stick, that $html into the response object and return it:

24 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 8
class GenusController extends Controller
{
... lines 11 - 13
public function showAction($genusName)
{
... lines 16 - 20
return new Response($html);
}
}

Go Deeper!

You can actually return anything from a controller via the kernel.view event: The kernel.view Event (advanced)

Create the Template

Ok, where do templates live? Ah, it's so simple: templates live in app/Resources/views. The one we're looking for will be in app/Resources/views/genus/show.html.twig. The existing index.html.twig template was for the original homepage. Check it out if you want to, then delete it!

Create a new genus directory and then a new file: show.html.twig. Welcome to Twig! You'll love it. Add an <h1> tag with The Genus and then {{ name }} to print the name variable. More on Twig in a second:

2 lines app/Resources/views/genus/show.html.twig
<h1>The Genus {{ name }}</h1>

But that's it! Refresh the browser. Check out that sweet h1 tag.

Now back up: we just did something really cool: used our first service. We now know that rendering a template isn't done by some deep, dark part of Symfony: it's done by the templating object. In fact, Symfony doesn't really do anything: everything is done by one of these services.

Leave a comment!

  • 2017-04-15 Tomislav Strugacevac

    I'll check it, thank you very much!

  • 2017-04-10 Diego Aguiar

    Hey Tomislav!

    Can you double check that your template lives in "app/Resources/views/genus/show.html.twig"
    if so, then you might be running in production mode, while in production, you have to clear cache every time you make a modification, but in this case you want to run in development mode, so you can forget about those pesky problems

    Have a nice day!

  • 2017-04-10 Diego Aguiar

    Hey Ross! I hope you had a great weekend :)

    Yeah the config files can be .yml or .xml, in your case are .yml
    Your configuration looks good to me, I'm not sure what is causing it to don't find the service
    Can you double check that you are not running in production mode ?
    In a controller method just do this, so you can be 100% sure:


    $env = $this->get('kernel')->getEnvironment();
    dump($env); die;


    Also run again composer install and clear the cache, just in case

    "I have aslo noticed, in my src folder, i dont have AppBundle\Directory\ClassName?"
    You don't have it because is just an example of how to point to a service class, by writting the full namespace of that class

    Cheers!

  • 2017-04-08 Tomislav Strugačevac

    Thank you for tutorials!

    Here's my problem:


    Unable to find template "genus/show.html.twig" (looked into: D:\xampp\htdocs\novi_symfony_projekt\app/Resources/views, D:\xampp\htdocs\novi_symfony_projekt\vendor\symfony\symfony\src\Symfony\Bridge\Twig/Resources/views/Form).
    500 Internal Server Error - InvalidArgumentException
    1 linked Exception: Twig_Error_Loader »

    Here's the code:

    namespace AppBundle\Controller;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Response;

    class GenusController extends Controller {
    /**
    * @route("/genus/{genusName}")
    */

    public function showAction($genusName)
    {
    $templating = $this->container->get('templating');
    $html = $templating->render('genus/show.html.twig', [
    'name' => $genusName

    ]);

    return new Response($html);
    }
    }

  • 2017-04-08 Ross Nolan

    I have aslo noticed, in my src folder, i dont have AppBundle\Directory\ClassName? just AppBundle\Controller, if that helps.

  • 2017-04-08 Ross Nolan

    Hows it going Diego, thanks for the reply! What config files? config.yml? i continued with the tutorial and have also implemented the database, but i am still geting the missing service, aslo put services.yml at the end of the post.


    namespace AppBundle\Controller;

    use AppBundle\Entity\Bibliography;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Response;

    //@Route("/Bibliography")
    /**
    * Class BibliographyController
    * @package AppBundle\Controller
    *
    */
    class BibliographyController extends Controller
    {
    /**
    * @Route("/Bibliography/new")
    */
    public function newAction()//order of route...
    {//adding to database
    $bib = new Bibliography();
    $bib->setName('Ross');
    $bib->setDif('Test');

    $entityManager = $this->getDoctrine()->getManager();//get manager
    $entityManager->persist($bib);//tell to save
    $entityManager->flush();// commit to the addition


    return new Response('<html><body>Added Ross to database</body></html>');
    }



    /**
    * @Route("/Bibliography/{wildcard}")
    */
    public function showAction($wildcard)
    {
    $notes = [
    'New entry: Latest technology in 3d accelerated hardware',
    'Old entry: A study on the latest in game graphics',
    'Admin entry: Update database'
    ];

    return $this->render('Bibliography/show.html.twig', [
    'name'=>$wildcard,
    'notes'=> $notes
    ]);
    }
    }

    Also i think the config files are config.yml?

    imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.yml }

    # Put parameters here that don't need to change on each machine where the app is deployed
    # http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
    parameters:
    locale: en

    framework:
    #esi: ~
    #translator: { fallbacks: ['%locale%'] }
    secret: '%secret%'
    router:
    resource: '%kernel.root_dir%/config/routing.yml'
    strict_requirements: ~
    form: ~
    csrf_protection: ~
    validation: { enable_annotations: true }
    #serializer: { enable_annotations: true }
    templating:
    engines: ['twig']
    default_locale: '%locale%'
    trusted_hosts: ~
    trusted_proxies: ~
    session:
    # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
    handler_id: session.handler.native_file
    save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
    fragments: ~
    http_method_override: true
    assets: ~
    php_errors:
    log: true

    # Twig Configuration
    twig:
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'

    # Doctrine Configuration
    doctrine:
    dbal:
    driver: pdo_mysql
    host: '%database_host%'
    port: '%database_port%'
    dbname: '%database_name%'
    user: '%database_user%'
    password: '%database_password%'
    charset: UTF8
    # if using pdo_sqlite as your database driver:
    # 1. add the path in parameters.yml
    # e.g. database_path: "%kernel.root_dir%/../var/data/data.sqlite"
    # 2. Uncomment database_path in parameters.yml.dist
    # 3. Uncomment next line:
    #path: '%database_path%'

    orm:
    auto_generate_proxy_classes: '%kernel.debug%'
    naming_strategy: doctrine.orm.naming_strategy.underscore
    auto_mapping: true

    # Swiftmailer Configuration
    swiftmailer:
    transport: '%mailer_transport%'
    host: '%mailer_host%'
    username: '%mailer_user%'
    password: '%mailer_password%'
    spool: { type: memory }

    Services...................

    # Learn more about services, parameters and containers at
    # http://symfony.com/doc/current/service_container.html
    parameters:
    #parameter_name: value

    services:
    #service_name:
    # class: AppBundle\Directory\ClassName
    # arguments: ['@another_service_name', 'plain_value', '%parameter_name%']
  • 2017-04-07 Diego Aguiar

    Hey Ross!

    If you are extending from BaseController (I can see you do) you can use "$this->render()" directly, anyways that error you are experimenting is kind of weird, are you using Symfony Standard Edition ?
    If you do, maybe there is a problem with your configuration, I'd like to see your configuration files so I can check if everything is ok.

    Have a nice day!

  • 2017-04-07 Ross Nolan

    Hows it going, im getting a missing service on 'templating', Ive excluded the cache directory aswell. If i continue with the tutorial , the site operates, but, i still have mising service?

    namespace AppBundle\Controller;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Response;

    class BibliographyController extends Controller
    {
    /**
    * @Route("/Bibliography/{wildcard}")
    */
    public function showAction($wildcard)
    {
    $templating = $this->container->get('templating');
    $html = $templating->render('Bibliography/show.html.twig',
    [
    'name'=>$wildcard
    ]);
    return new Response($html);
    }
    }

  • 2016-06-20 Victor Bocharsky

    Hey, Jelle!

    BTW, if you would love to be a PhpStorm beta tester, you could use it for free. Check PhpStorm Early Access Program, each month you will be able to download a new build of PhpStorm beta. It's a new fully featured version of PhpStorm, which will be released in the future. You need to update it to each time when new build is released to renew your trial licence.

    Cheers!

  • 2016-06-18 Jelle Schouwstra

    Aah Yes, I see it now! Thanks a bunch! I would love to use PHPstorm if it was free to use, in contrast to Netbeans, that's why my choice has fallen on the last mentioned

  • 2016-06-18 weaverryan

    Hey Jelle!

    Ya know, the hardest things to track down are the smallest mistakes. And this is small! :)

    Here's the problem:


    // you have
    $templating->$this->container->get('templating');

    // you should have
    $templating = $this->container->get('templating');

    That's it :). Hence the undefined variable error - it thinks you're trying to use a $templating variable, not set one! Your editor should highlight this for you with an error. I like PhpStorm better than NetBeans, but that's a solid editor - check to see if there's any red highlighting around this area that would help you spot things like this.

    Cheers!

  • 2016-06-18 Jelle Schouwstra

    Great tutorial, one problem though, I get "Notice: Undefined variable: templating"

    And an exception:
    Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Notice: Undefined variable: templating" at D:\media\sandbox\test\symfony2016\src\AppBundle\Controller\GenusController.php line 23
    Context: [exception => Object(Symfony\Component\Debug\Exception\ContextErrorException)]

    I've followed your tutorial and this is my controller code:

    namespace AppBundle\Controller;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

    use Symfony\Component\HttpFoundation\Response;

    * @Route("/genus/{genusName}")
    * */
    public function showAction($genusName){
    $templating->$this->container->get('templating');
    $html = $templating->render('genus/show.html.twig', [
    'name' => $genusName
    ]);
    I don't use phpstorm but Netbeans, perhaps I used a wrong namespace?

  • 2016-05-18 Konrad Zając

    Ok, thanks !

  • 2016-05-18 weaverryan

    Hi Konrad!

    Good job debugging :). The problem is that auto-complete will suggest *both* classes. For PhpStorm, it sees 2 classes that are named Controller. It doesn't know which one is the correct class and which one is the incorrect class - it simply suggests to you both classes that are called "Controller". The one in HttpKernel\Tests is just an internal helper class for helping core Symfony run its own tests. But, PhpStorm doesn't know this.

    Usually, when you use auto-complete, there is only 1 matching class name, but sometimes there are multiple, so you need to be careful to choose the correct one. Another example is Symfony's Response class. There are two: one in the HttpFoundation component (this is the one you want) and one in the BrowserKit component (not the one you want).

    I hope this clarifies!

  • 2016-05-14 Konrad Zając

    Hi,
    Firstly great tuturial ;)
    Secondly I did all the steps but I get
    "Attempted to load class "Controller" from namespace "Symfony\Component\HttpKernel\Tests".
    Did you forget a "use" statement for "Symfony\Bundle\FrameworkBundle\Controller\Controller"?"

    my use statement after autocompletion is use Symfony\Component\HttpKernel\Tests\Controller;
    and in this tutorial it's use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    what's the correct way?

    ---------Edit-------------

    OK, pasted the second one and it works - but still , why does my autcompletion gives me the wrong one?

  • 2016-05-04 Jay

    Oh my, sorry I didn't notice that :) it's now working thanks for the help!

  • 2016-05-03 weaverryan

    Hi Jay!

    You're getting some really good errors - because these are some of the most common errors. So once you learn how to read them, you'll be dangerous :).

    For this error, Symfony is literally saying: "I looked through all of your routes, but didn't find any that match the URL /". Remember, you can get a list of *all* of the routes you've built by running:


    bin/console debug:router

    In this case, we have not (at this point) created an route for the URL "/". Try changing the URL in your browser to http://localhost:8000/genus/octopus.

    You may have other errors when you get to that page, but this is definitely the cause of *this* error. About the auto-completing, do you have the Symfony plugin installed *and* enabled for this project?

    Cheers!

  • 2016-05-03 Jay

    Thanks for the reply! the path is correct and now i'm getting this error:
    No route found for "GET /"
    404 Not Found - NotFoundHttpException
    1 linked Exception:
    ResourceNotFoundException ».

    The cache is already mark as excluded but the auto-complete in get('templating') doesn't appear even when i'm pressing ctrl + spacebar and it says "No suggestions".
    i still get the error even if i change the code into this:
    return $this->render('genus/show.html.twig', [
    'name' => $genusName
    ]);

  • 2016-05-02 weaverryan

    Hi Jay!

    You should ignore the "Typo" - that's PhpStorm being *too* helpful. It literally is saying "The word templating isn't a real English word, maybe it's a typo?". It *is* the correct "service id" to use :).

    About your error, you can see that the templating service is *trying* render your template, but it can't find it. This literally means that it's looking for it at app/Resources/views/genus/show.html.twig, but it doesn't see a file there. Double-check that you have that file (and that it's in the right location) - that's probably the error. But let me know either way

    Cheers!

  • 2016-05-02 Jay

    why am i getting this error "Typo: In word 'templating'
    Spellchecker inspection helps locate typos and misspelling in your code, comments and literals, and fix them in one click."

    i get that in this code:
    $templating = $this->container->get('templating');

    i followed the instruction but the auto complete in templating didnt show.
    and this is the error when i load the page:

    Unable to find template "genus/show.html.twig" (looked into: C:\wamp\bin\aqua_note\app/Resources/views, C:\wamp\bin\aqua_note\vendor\symfony\symfony\src\Symfony\Bridge\Twig/Resources/views/Form).

    500 Internal Server Error - InvalidArgumentException

    1 linked Exception:

    Twig_Error_Loader »