企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### **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'); } } ~~~