### **Decoupling Handlers** ###
To get started, let's jump right into an example. Consider a queue handler that sends an SMS message to a user. After sending the message, the handler logs that message so we can keep a history of all SMS messages we have sent to that user. The code might look something like this:
~~~
<!-- lang:php -->
class SendSMS{
public function fire($job, $data)
{
$twilio = new Twilio_SMS($apiKey);
$twilio->sendTextMessage(array(
'to'=> $data['user']['phone_number'],
'message'=> $data['message'],
));
$user = User::find($data['user']['id']);
$user->messages()->create(array(
'to'=> $data['user']['phone_number'],
'message'=> $data['message'],
));
$job->delete();
}
}
~~~
Just by examining this class, you can probably spot several problems. First, it is hard to test. The ``Twilio_SMS`` class is instantiated inside of the *fire* method, meaning we will not be able to inject a mock service. Secondly, we are using Eloquent directly in the handler, thus creating a second testing problem as we will have to hit a real database to test this code. Finally, we are unable to send SMS messages outside of the queue. All of our SMS sending logic is tightly coupled to the Laravel queue.
By extracting this logic into a separate "service" class, we can decouple our application's SMS sending logic from Laravel's queue. This will allow us to send SMS messages from anywhere in our application. While we are decoupling this process from the queue, we will also refactor it to be more testable.
So, let's examine an alternative:
~~~
<!-- lang:php -->
class User extends Eloquent {
/**
* Send the User an SMS message
*
* @param SmsCourierInterface $courier
* @param string $message
* @return SmsMessage
*/
public function sendSmsMessage(SmsCourierInterface $courier, $message)
{
$courier->sendMessage($this->phone_number, $message);
return $this->sms()->create(array(
'to'=> $this->phone_number,
'message'=> $message,
));
}
}
~~~
In this refactored example, we have extracted the SMS sending logic into the ``User`` model. We are also injecting a ``SmsCourierInterface`` implementation into the method, allowing us to better test that aspect of the process. Now that we have refactored this logic, let's re-write our queue handler:
~~~
<!-- lang:php -->
class SendSMS {
public function __construct(UserRepository $users, SmsCourierInterface $courier)
{
$this->users = $users;
$this->courier = $courier;
}
public function fire($job, $data)
{
$user = $this->users->find($data['user']['id']);
$user->sendSmsMessage($this->courier, $data['message']);
$job->delete();
}
}
~~~
As you can see in this refactored example, our queue handler is now much lighter. It essentially serves as a translation layer between the queue and your real application logic. That is great! It means that we can easily send SMS message s outside of the queue context. Finally, let's write some tests for our SMS sending logic:
~~~
<!-- lang:php -->
class SmsTest extends PHPUnit_Framework_TestCase {
public function testUserCanBeSentSmsMessages()
{
/**
* Arrage ...
*/
$user = Mockery::mock('User[sms]');
$relation = Mockery::mock('StdClass');
$courier = Mockery::mock('SmsCourierInterface');
$user->shouldReceive('sms')->once()->andReturn($relation);
$relation->shouldReceive('create')->once()->with(array(
'to' => '555-555-5555',
'message' => 'Test',
));
$courier->shouldReceive('sendMessage')->once()->with(
'555-555-5555', 'Test'
);
/**
* Act ...
*/
$user->sms_number = '555-555-5555'; //译者注: 应当为 phone_number
$user->sendMessage($courier, 'Test');
}
}
~~~
- Dependency Injection
- The Problem
- Build A Contract
- Take It further
- Too Much Java?
- The IoC Container
- Basic Binding
- Reflective Resolution
- Interface As Contract
- Strong Typing & Water Fowl
- A Contract Example
- Interface & Team Development
- Service Provider
- As Bootstrapper
- As Organizer
- Booting Providers
- Providing The Core
- Application Structure
- MVC Is Killing You
- Bye, Bye Models
- It's All About The Layers
- Where To Put "Stuff"
- Applied Architecture: Decoupling Handles
- Decoupling Handlers
- Other Handlers
- Extending The Framework
- Manager & Factories
- Cache
- Session
- Authentication
- IoC Based Extension
- Request Extension
- Single Responsibility Principle
- Open Closed Principle