Our app is small now, but as it grows, the app.php file will get harder and harder to read. The best way to fix this is to separate each different chunk of functionality into different PHP classes and methods. Each of these classes is called a "service" and the whole idea is sometimes called Service-Oriented Architecture.

Create a new file in src/DiDemo called FriendHarvester.php, which will be responsible for sending the email to every lucky person in the database:

11 lines src/DiDemo/FriendHarvester.php
... lines 1 - 2
namespace DiDemo;
class FriendHarvester
{
... lines 7 - 10
}

Add the namespace so that it follows the directory structure and give it an emailFriends method:

11 lines src/DiDemo/FriendHarvester.php
... lines 1 - 6
public function emailFriends()
{
}

Copy in all of our logic into this new method:

28 lines src/DiDemo/FriendHarvester.php
... lines 1 - 10
$mailer = new SmtpMailer('smtp.SendMoneyToStrangers.com', 'smtpuser', 'smtppass', '465');
$sql = 'SELECT * FROM people_to_spam';
foreach ($pdo->query($sql) as $row) {
$mailer->sendMessage(
$row['email'],
'Yay! We want to send you money for no reason!',
sprintf(<<<EOF
Hi %s! We've decided that we want to send you money for no reason!
Please forward us all your personal information so we can make a deposit and don't ask any questions!
EOF
, $row['name']),
'YourTrustedFriend@SendMoneyToStrangers.com'
);
}
... lines 27 - 28

Go Deeper!

To learn more about PHP namespaces, check out our free PHP Namespaces in 120 Seconds tutorial

Tip

The namespace follows the directory structure so the the class is automatically autoloaded by Composer's autoloader. For more on how this all works, see Autoloading in PHP and the PSR-0 Standard.

And just like that, you've created your first service! Roughly speaking, a service is any PHP class that performs an action. Since this sends emails to our new soon-to-be-rich friends, it's a service.

Tip

An example of a PHP class that's not a service would be something that simply holds data, like a Blog class, with title, author and body fields. These are sometimes called "Model objects".

The app.php code gets pretty simple now: just instantiate the FriendHarvester and call the method:

13 lines app.php
... lines 1 - 5
use DiDemo\FriendHarvester;
... lines 7 - 10
$friendHarvester = new FriendHarvester();
$friendHarvester->emailFriends();

But when we try it:

php app.php

We get a huge error!

Once we've moved the code, we don't have access to the PDO object anymore. So how can we get it?

Accessing External Objects from a Service

This is our first important crossroads. There are a few cheating ways to do this, like using the dreaded global keyword:

30 lines src/DiDemo/FriendHarvester.php
... lines 1 - 8
public function emailFriends()
{
// NOOOOOOOO!!!!
global $pdo;
... lines 13 - 28
}

Don't use this. You could also make the $pdo variable available statically, by creating some class and then reference it:

19 lines app.php
... lines 1 - 10
class Registry
{
static public $pdo;
}
Registry::$pdo = $pdo;
$friendHarvester = new FriendHarvester();
... lines 18 - 19

30 lines src/DiDemo/FriendHarvester.php
... lines 1 - 8
public function emailFriends()
{
// Still NOOOOOOOO!!!!
$pdo = \Registry::$pdo;
... lines 13 - 28
}

The problem with both approaches is that our FriendHarvester has to assume the $pdo variable has actually been set and is available. Or to say it differently, when you use this class, you need to make sure any global or static variables it needs are setup. And the only way to know what the class needs is to scan the file looking for global or static variable calls. This makes FriendHarvester harder to understand and maintain, and much harder to test.

Our Friend Dependency Injection

Let's get rid of all of that and do this right.

Since FriendHarvester needs the PDO object, add a __construct() method with it as the first argument. Set the value to a new private property and update our code to use it:

35 lines src/DiDemo/FriendHarvester.php
... lines 1 - 6
class FriendHarvester
{
private $pdo;
public function __construct($pdo)
{
$this->pdo = $pdo;
}
public function emailFriends()
{
... lines 18 - 20
foreach ($this->pdo->query($sql) as $row) {
... lines 22 - 32
}
}
}

The FriendHarvester now makes a lot of sense: whoever instantiates it must pass us a $pdo variable. Inside this class, we don't care how this will happen, we just know that it will, and we can make use of it.

Tip

You can also type-hint the argument, which is a great practice. We'll talk more about this later:

public function __construct(\PDO $pdo)

This very simple idea is called Dependency Injection, and you just nailed it! Dependency injection means that if a class needs an object or some configuration, we force that information to be passed into that class, instead of reaching outside of it by using a global or static variable.

Back in app.php, we now need to explicitly pass the PDO object when instantiating the FriendHarvester:

13 lines app.php
... lines 1 - 10
$friendHarvester = new FriendHarvester($pdo);
... lines 12 - 13

Run it:

php app.php

Everything works exactly like before, except that we've moved our logic into a service, which makes it testable, reusable, and much more understandable for two reasons.

First, the class and method names (FriendHarvester::emailFriends()) serve as documentation for what our code does. Second, because we're using dependency injection, it's clear what our service might do, because we can see what outside things it needs.

Leave a comment!

  • 2016-05-02 weaverryan

    Hi Oscar!

    This is a question that strikes *right* at the heart of the purpose of services :). You could keep your life simple and code 100% in your controller. There are a few disadvantages to this:

    1) If you have logic in your controller, it can't be re-used on other pages (i.e. other controllers). This is the *biggest* reason.
    2) If you have 50 lines of code in your controller, I need to read them to understand what they're doing. But, if you move them into a separate class - perhaps ImageHelper - and in a method - perhaps createThumbnail() - it becomes a lot more obvious *what* those 50 lines do. Organizing into services starts to naturally organize and self-document your code. This subtle advantage is one of my favorite
    3) If you like to unit test, you need to move code into a separate spot.

    Moving code from your controller into a service helps with each of these. Does that help?

    Cheers!

  • 2016-05-02 Oscar Untied

    Why do we split, the service from the controller? What can i see as the difference between a controller and a service now? That a controller can call a service multiple times depending on what the logic would tell the controller to do, and that the service only does work once for each call?

  • 2015-02-05 Diego Aguiar

    Nice, I'll go that way

    Thx :)

  • 2015-02-04 weaverryan

    Hi Diego!

    You *can* do this, but you shouldn't :). Basically, the repository should *only* have queries. If you're doing something in there that needs an additional service, then it's probably something that no longer belongs in the repository. For these things, I very commonly create "Managers" - e.g. PostManager (if Post is your entity). You can certainly have the PostManager call the PostRepository (just inject the entity manager, I do this all the time).

    Hope that helps!

  • 2015-02-04 Diego Aguiar

    Hi there!

    It's me again :D

    I was wondering how can you inject services into Entity Repository.

    I made a service for my user's entity in order to handle my custom ID generator and I would like to inject it to my repositories.

    Thanks in advance :)