This tutorial has a new version, check it out!

Dependency Inject All the Things

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Dependency Inject All the Things

The CSV returns the id, name and time of each event. Let’s pretend that someone is using this to double-check the accuracy of updated events. To make their life easier, I want to also return the URL to each event.

So how do we generate URL’s? In EventController, we used the generateUrl function:

$this->generateUrl('event_show', array('slug' => $entity->getSlug()))

So let’s try putting that into EventReportManager and seeing what happens:

// src/Yoda/EventBundle/Reporting/EventReportManager.php
// ...

public function getRecentlyUpdatedReport()
{
    // ...

    foreach ($events as $event) {
        $data = array(
            $event->getId(),
            $event->getName(),
            $event->getTime()->format('Y-m-d H:i:s'),
            $this->generateUrl('event_show', array('slug' => $event->getSlug()))
        );

        $rows[] = implode(',', $data);
    }

    return implode("\n", $rows);
}

Let’s try it. Ah, no download - just an ugly error:

Call to undefined method YodaEventBundleReportingEventReportManager::generateUrl()

We made this mistake before - generateUrl lives in Symfony’s Controller, and we don’t have access to it here. Open up that function to remember what it actually does:

// vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
// ...

public function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
    return $this->container->get('router')
        ->generate($route, $parameters, $referenceType);
}

This tells me that if I want to generate a URL, I actually need the router service. So how can we get the router service inside EventReportManager? You know the secret: dependency injection.

Add a second constructor argument and a second class property:

// src/Yoda/EventBundle/Reporting/EventReportManager.php
// ...

use Doctrine\ORM\EntityManager;
use Symfony\Component\Routing\Router;

class EventReportManager
{
    private $em;

    private $router;

    public function __construct(EntityManager $em, Router $router)
    {
        $this->em = $em;
        $this->router = $router;
    }

    // ...
}

This time, I guessed the router class name for the type-hint. Now that we have the router, just use it in the function:

// src/Yoda/EventBundle/Reporting/EventReportManager.php
// ...

public function getRecentlyUpdatedReport()
{
    // ...

    foreach ($events as $event) {
        $data = array(
            $event->getId(),
            $event->getName(),
            $event->getTime()->format('Y-m-d H:i:s'),
            $this->router->generate('event_show', array('slug' => $event->getSlug()))
        );

        $rows[] = implode(',', $data);
    }

    return implode("\n", $rows);
}

Ok, let’s test it. Great, now we get a different error:

Catchable Fatal Error: Argument 2 passed to YodaEventBundleReportingEventReportManager::__construct() must be an instance of SymfonyComponentRoutingRouter, none given

Read it closely. It says that something is calling __construct on our class but passing it nothing for the second argument. Of course: we forgot to tell the container about this new argument. Open the services.yml file and add a second item to arguments:

services:
    event_report_manager:
        class: Yoda\EventBundle\Reporting\EventReportManager
        arguments: ["@doctrine.orm.entity_manager", "@router"]

Now, we get the download again. Open up the CSV. Hey, we have URL’s!

5,Darth's Birthday Party!,2014-07-24 12:00:00,/darth-s-birthday-party/show
6,Rebellion Fundraiser Bake Sale!,2014-07-24 12:00:00,/rebellion-fundraiser-bake-sale/show

Woops! The URLs aren’t helpful unless they’re absolute. Pass true as the third argument to generate to make this happen:

// src/Yoda/EventBundle/Reporting/EventReportManager.php
// ...

$data = array(
    $event->getId(),
    $event->getName(),
    $event->getTime()->format('Y-m-d H:i:s'),
    $this->router->generate(
        'event_show',
        array('slug' => $event->getSlug()),
        true
    )
);

Download another file and open it up. Perfect!

Here are the huge takeaways. When you’re in a service and you need to do some work, just find out which service does that work, inject it through the constructor, then use it. You’ll use this pattern over and over again. Understand this, and you’ve mastered the most important concept in Symfony.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "~2.4", // v2.4.2
        "doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
        "doctrine/doctrine-bundle": "~1.2", // v1.2.0
        "twig/extensions": "~1.0", // v1.0.1
        "symfony/assetic-bundle": "~2.3", // v2.3.0
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.5
        "symfony/monolog-bundle": "~2.4", // v2.5.0
        "sensio/distribution-bundle": "~2.3", // v2.3.4
        "sensio/framework-extra-bundle": "~3.0", // v3.0.0
        "sensio/generator-bundle": "~2.3", // v2.3.4
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "doctrine/doctrine-fixtures-bundle": "~2.2.0", // v2.2.0
        "ircmaxell/password-compat": "~1.0.3", // 1.0.3
        "phpunit/phpunit": "~4.1", // 4.1.0
        "stof/doctrine-extensions-bundle": "~1.1.0" // v1.1.0
    }
}