Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Injecting Config & Services and using Interfaces

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

We've already created our first service and used dependency injection, we're even closer to getting this money out! One problem with the FriendHarvester is that we've hardcoded the SMTP configuration inside of it:

... lines 1 - 15
public function emailFriends()
{
$mailer = new SmtpMailer('smtp.SendMoneyToStrangers.com', 'smtpuser', 'smtppass', '465');
... lines 19 - 33
}

What if we want to re-use this class with a different configuration? Or what if our beta and production setups use different SMTP servers? Right now, both are impossible!

Injecting Configuration

When we realized that FriendHarvester needed the PDO object, we injected it via the constructor. The same rule applies to configuration. Add a second constructor argument, which will be an array of SMTP config and update the code to use it:

... lines 1 - 6
class FriendHarvester
{
private $pdo;
private $smtpConfig;
public function __construct($pdo, array $smtpConfig)
{
$this->pdo = $pdo;
$this->smtpConfig = $smtpConfig;
}
public function emailFriends()
{
$mailer = new SmtpMailer(
$this->smtpConfig['server'],
$this->smtpConfig['user'],
$this->smtpConfig['password'],
$this->smtpConfig['port']
);
... lines 27 - 41
}
}

Back in app.php, pass the array when creating FriendHarvester:

18 lines app.php
... lines 1 - 10
$friendHarvester = new FriendHarvester($pdo, array(
'server' => 'smtp.SendMoneyToStrangers.com',
'user' => 'smtpuser',
'password' => 'smtppass',
'port' => '465'
));
... lines 17 - 18

When we try it:

php app.php

It still works! Our class is more flexible now, but, let's level up again!

Injecting the Whole Mailer

We can now configure the FriendHarvester with different SMTP settings, but what if we wanted to change how mails are sent entirely, like from SMTP to sendmail? And what if we needed to use the mailer object somewhere else in our app? Right now, we would need to create it anywhere we need it, since it's buried inside FriendHarvester.

In fact, FriendHarvester doesn't really care how we're sending emails, it only cares that it has an SmtpMailer object so that it can call sendMessage(). So like with the PDO object, it's a dependency. Refactor our class to pass in the whole SmtpMailer object instead of just its configuration:

... lines 1 - 6
class FriendHarvester
{
private $pdo;
private $mailer;
public function __construct($pdo, $mailer)
{
$this->pdo = $pdo;
$this->mailer = $mailer;
}
public function emailFriends()
{
... line 21
foreach ($this->pdo->query($sql) as $row) {
$this->mailer->sendMessage(
... lines 24 - 32
);
}
}
}

Update app.php to create the mailer object:

20 lines app.php
... lines 1 - 10
$mailer = new SmtpMailer(
'smtp.SendMoneyToStrangers.com',
'smtpuser',
'smtppass',
'465'
);
$friendHarvester = new FriendHarvester($pdo, $mailer);
... lines 19 - 20

Try it out to make sure it still works:

php app.php

We would hate for our friends to miss this opportunity!

Once again, this makes the FriendHarvester even more flexible and readable, and will also make re-using the mailer possible. As a general rule, it's almost always better to inject a service into another than to create it internally. When you're in a service, think twice before using the new keyword, unless you're instantiating a simple object that exists just to hold data as opposed to doing some job (i.e. a "model object").

Type-Hinting

One thing we've neglected to do is type-hint our two constructor arguments. Let's do it now:

... lines 1 - 6
class FriendHarvester
{
... lines 9 - 12
public function __construct(\PDO $pdo, SmtpMailer $mailer)
... lines 14 - 35
}

This is totally optional, but has a bunch of benefits. First, if you pass something else in, you'll get a much clearer error message. Second, it documents the class even further. A developer now knows exactly what methods she can call on these objects. And third, if you use an IDE, this gives you auto-completion! Type-hinting is optional, but I highly recommend it.

Adding an Interface

Right now we're injecting an SmtpMailer. But in reality, FriendHarvester only cares that the mailer has a sendMessage() method on it. But even if we had another class with an identical method, like SendMailMailer, for example, we couldn't use it because of the specific type-hint.

To make this more awesome, create a new MailerInterface.php file, which holds an interface with the single send method that all mailers must have:

... lines 1 - 2
namespace DiDemo\Mailer;
interface MailerInterface
{
public function sendMessage($recipientEmail, $subject, $message, $from);
}

Update SmtpMailer to implement the interface and change the type-hint in FriendHarvester as well:

... lines 1 - 7
class SmtpMailer implements MailerInterface
{
... lines 10 - 59
}

... lines 1 - 4
use DiDemo\Mailer\MailerInterface;
class FriendHarvester
{
... lines 9 - 12
public function __construct(\PDO $pdo, MailerInterface $mailer)
{
... lines 15 - 16
}
... lines 18 - 35
}

When you're finished, try the application again:

php app.php

Everything should still work just fine. And with any luck you will find a place for all of that annoying money.

Just like with every step so far, this has a few great advantages. First, FriendHarvester is more flexible since it now accepts any object that implements MailerInterface. Second, it documents our code a bit more. It's clear now exactly what small functionality FriendHarvester actually needs. Finally, in SmtpMailer, the fact that it implements an interface with a sendMessage() method tells us that this method is particularly important. The class could have other methods, but sendMessage() is probably an especially important one to focus on.

Leave a comment!

2
Login or Register to join the conversation
Terrence Avatar
Terrence Avatar Terrence | posted 6 months ago | edited

I'm getting this error on challenge #3. I even copied the answers into the challenge to make sure I didn't miss anything and it didn't fix it.

Error! This PHP error is coming from your code. Read it carefully to debug!
( ! ) Fatal error: Interface 'EmailLoaderInterface' not found in EmailAddressLoader.php on line 3
Call Stack
#TimeMemoryFunctionLocation
10.0050813200{main}( ).../sendHappy.php:0
... See the Browser below for the full output.

Reply

Hey Terrence,

We're sorry to hear you have trouble with our challenges. Sometimes it may happen that challenge instance is broken and stop saving your changes. Please, wait until the challenge is shut down, refresh the page and try again - it should work now because the system will create a new challenge instance.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

userVoice