Chapter 3. Building Services, Commands, and Events

In the first two chapters, we set up the basic structure of our accommodation reservation system. We designed our classes, created our database schema, and learned how to test them. Now we need to translate the business requirements into code.

In this chapter, we will cover the following topics:

  • Commands
  • Events
  • Command handlers
  • Event handlers
  • Queued event handlers
  • Queued commands
  • Console commands
  • The command scheduler

Request routing

As mentioned earlier, Laravel 5 has adopted the command bus pattern. Laravel 4 viewed commands as something to be executed from the command line, whereas in Laravel 5, a command can be used in any context, allowing excellent reuse of code.

The following is an example of the Laravel 4 HTTP request flow:

Request routing

Here is an example of the Laravel 5 HTTP request flow:

Request routing

The first image illustrates the Laravel 4 request flow. The request via HTTP was handled by the router, and then sent to the controller, where generally, we could then talk to either the repository or directory of the model. In Laravel 5, this is still possible; however, as shown in the second image, we can see that the ability to add additional blocks, layers, or modules allows us to separate the life cycle of the request into individual isolated pieces. Laravel 4 allowed us to put all of our code to handle the request inside the controller, while in Laravel 5, we are free to do the same, although now we are also able to easily separate the request into various pieces. Some of these concepts are derived from Domain-driven Design (DDD).

Inside the controller, the command is instantiated using the Data Transfer Object (DTO) paradigm. Then, the command is sent to the command bus, where it is handled by a handler class, which has two methods: __construct() and handle(). Inside the handler, we fire or instantiate an event. The event is likewise handled in the same way by an event handler method with two methods: __construct() and handle().

The directory structure is very clean and looks like this:

/app/Commands
/app/Events/
/app/Handlers/
/app/Handlers/Commands
/app/Handlers/Events
/app/HTTP/Controllers

It's rather self-explanatory; the commands and events are in their respective directories, while the handlers for each have their own directories.

Note

Laravel 5.1 has changed the name of the app/Commands directory to app/Jobs to ensure that programmers do not get the concepts of the command bus and console commands mixed up.

User stories

The idea for the command component can easily derive from a user story or a task required by a user to achieve a goal. The most simple example would be to search for a room:

As a hotel website user,
I want to search for a room
so that I can select from a list of results.

User stories, deriving from the agile methodology, guarantee that the code written closely matches the business requirement. They often follow the pattern "As a… I want to… so that...". This defines the actor, the intent, and the benefit. It helps us plan how each task will be converted into code. In our example, the user stories can transform into tasks.

As a hotel website user, I would create a list of the following tasks:

  1. As a hotel website user, I want to search for a room so that I can select a room from a list of results.
  2. As a hotel website user, I want to reserve a room so that I can stay at the hotel.
  3. As a hotel website user, I want to receive an e-mail with the reservation details so that I can have a copy of the reservation.
  4. As a hotel website user, I want to be on a waiting list so that I can reserve a room when one becomes available.
  5. As a hotel website user, I want to get notified of the room availability so that I can reserve a room.

User stories to code

The first task, searching for a room, would most likely be a RESTful call from a user or from an external service, so this task would be exposed to our controllers and thus, to our RESTful API.

The second task, reserving a room, is a similar action initiated by the user or the other service. This task may require the user to be logged in.

The third task could depend on the second task. This task requires an interaction with another process that sends the user a confirmation e-mail with the details of the booking. We can also write this as: As a hotel website, I want to send an e-mail with the reservation details, so that he or she may have a copy of the reservation.

The fourth task, getting placed on the waiting list, could be a command that is executed after the request to reserve a room is launched; in the case of another user reserving the room at the same time. It would most likely be called from the application itself, not the user, since the user has no knowledge of the real-time accommodation inventory. This could help us handle a race condition. Also, we should assume that when the website user is deciding which room to reserve, there is no locking mechanism on that room that would guarantee the availability. We could also write this as: As a hotel website, I want to put a user on the waiting list so that they can be notified when a room is available.

For the fifth task, as the user is put on a waiting list, the user could also be notified of the room as it becomes available. This action checks for the availability of the rooms, and then checks for any users on the waiting list. The user story can be rewritten as follows: As a hotel website, I want to notify a waiting list user of the availability of the rooms so that he or she may reserve a room. If a room becomes available, the first user on the waiting list will be notified of the availability via an e-mail. This command would be executed frequently, as if it is a cron job. Luckily, Laravel 5 has a new mechanism to allow commands to be executed at a given frequency.

It becomes apparent that if the user story has to be written with both using the website as the actor ("As a hotel website...") or the website user as the actor ("As a hotel website user..."), a command is useful and can be launched either from the RESTful API (user side) or from within the Laravel application itself.

Since our first task most likely involves an external service, we will create a route and also a controller to handle the request.

The controller

The first step involves the creation of a route, and the second step involves the creation of a controller.

Searching for the room

First, let's create a route in the routes.php file and map it to the controller method as follows:

Route::get('search', 'RoomController@search'),

The request parameters, such as the start/end dates and location details will be as follows:

{
  "start_date": "2015-07-10"
  "end_date": "2015-07-17"
  "city": "London"
  "country": "England"
}

The search parameters will be sent as JSON-encoded objects. They will be sent as follows:

http://websiteurl.com/search?query={%22start_date%22:%222015-07-10%22,%22end_date%22:%222015-07-17%22,%22city%22:%22London%22,%22country%22:%22England%22}

Now, let's add a search method to our room controller to handle the JSON input in the case of a request that comes in as an object, as follows:

/**
* Search for a room in an accommodation
*/
public function search()
{
      json_decode(Request::input('query'));
}

The request facade handles the input variable query, and then decodes its JSON structure into an object.

In Chapter 4, Creating RESTful APIs, we will complete the code for the search method, but for now, we will simply create the architecture of this part of our RESTful API system.

Controller to command

For the second task, reserving the room, we'll create a command as we'll most likely need a follow up action, which we will enable via the publisher subscriber pattern. The publisher subscriber pattern is used to represent publishers that send messages and subscribers that listen to these messages.

Add the route to routes.php as follows:

Route::post('reserve-room', 'RoomController@store'),

We map the post to the room controller's store method; this will create the reservation. Remember that we created the command like this:

$ php artisan make:commandReserveRoomCommand -–handler

Our ReserveRoomCommand class looks like this:

<?php namespace MyCompanyCommands;

use MyCompanyCommandsCommand;
use MyCompanyUser;

class ReserveRoomCommand extends Command {

    public $user;
    public $rooms;
    public $start_date;
    public $end_date;

    /**
    * Create a new command instance.
    *
    * @return void
    */
    public function __construct(User $user, $start_date, $end_date, $rooms)
    {
        $this->rooms = $rooms;
        $this->user = $user;
        $this->start_date = $start_date;
        $this->end_date = $end_date;
     }

}

We need to add the following attributes to the constructor:

    public $user;
    public $rooms;
    public $start_date;
    public $end_date;

Also, add the following assignments to the constructor:

        $this->rooms = $rooms;
        $this->user = $user;
        $this->start_date = $start_date;
        $this->end_date = $end_date;

This allows us to carry the values through.

Command to event

Now let's create an event. Use artisan to create an event, RoomWasReserved, which is to be fired when the room gets created:

$ phpartisan make:eventRoomWasReserved

The RoomWasReserved event class looks like the following code snippet:

<?php namespace MyCompanyEvents;

use MyCompanyAccommodationReservation;
use MyCompanyEventsEvent;
use MyCompanyUser;

use IlluminateQueueSerializesModels;

class RoomWasReserved extends Event {

    use SerializesModels;
    
    private $user;
    private $reservation;
    
    /**
    * Create a new event instance.
    *
    * @return void
    */
    public function __construct(User $user, Reservation $reservation)
    {
        $this->user = $user;
        $this->reservation = $reservation;
    }
}

We'll tell it to use the MyCompanyAccommodationReservation and MyCompanyUser entities so that we can pass them to the constructor. Inside the constructor, we assign them to entities within the event object.

Now, let's fire the event from inside the command handler. Laravel provides you with a simple event() method as a convenience/helper method that will fire an event. We'll inject the RoomWasReserved event with the instantiated reservation and user as follows:

event(new RoomWasReserved($user, $reservation));

The ReserveRoomCommandHandler class

Our ReserveRoomCommandHandler class now instantiates a new reservation, uses the createNew factory method to inject the dependencies, and finally, fires the RoomWasReserved event as follows:

<?phpnamespace MyCompanyHandlersCommands;

use MyCompanyCommandsReserveRoomCommand;

use IlluminateQueueInteractsWithQueue;

class ReserveRoomCommandHandler {

    /**
    * Create the command handler.
    *
    * @return void
    */
    public function __construct()
    {
        //
    }

    /**
    * Handle the command.
    *
    * @paramReserveRoomCommand  $command
    * @return void
    */
    public function handle(ReserveRoomCommand $command)
    {
        
        $reservationValidator = new MyCompanyAccommodationReservationValidator();
        
        if ($reservationValidator->validate($command->start_date,$command->end_date,$command->rooms)) {
              $reservation = 
                $reservationRepository->create(
                ['date_start'=>$command->$command→start_date,
                'date_end'=>$command->end_date,
                'rooms'=>$command->'rooms']);
        }
    $reservation = new 
      event(new RoomWasReserved($command->user,$reservation));
    }
}

Event to handler

Now, we need to create the event handler. As you would have expected, the Artisan provides a convenient way of doing this, although the syntax is a bit different. This time, strangely, the word make doesn't appear in the phrase:

$ php artisan handler:eventRoomReservedEmail --event=RoomWasReserved
    <?php namespace MyCompanyHandlersEvents;
    
    use MyCompanyEventsRoomWasReserved;
    
    use IlluminateQueueInteractsWithQueue;
    use IlluminateContractsQueueShouldBeQueued;
    
    class RoomReservedEmail {
    
        /**
        * Create the event handler.
        * @return void
        */
        public function __construct()
        {
        }
    
        public function handle(RoomWasReserved $event)
        {
            //TODO: send email to $event->user
            //TODO: with details about $event->reservation;
        }
    }

Now we need to connect the event to its listener. We will edit the app/Providers/EventServiceProvider.php file as follows:

protected $listen = [
    'MyCompanyEventsRoomWasReserved' => [
      'MyCompanyHandlersEventsRoomReservedEmail',
      ],
    ];

As shown in the preceding code snippet, we will add the key-value pair to the $listen array. The full path, as shown, is needed for the key, the event name, and the array of handlers. In this case, we only have one handler.

Queued event handlers

If we would like to not have the event handled immediately, but rather, put into the queue, we can add –queued to the create command as follows:

$ php artisan handler:eventRoomReservedEmail --event=RoomWasReserved --queued
    <?php namespace MyCompanyHandlersEvents;
    
    use MyCompanyEventsRoomWasReserved;
    
    use IlluminateQueueInteractsWithQueue;
    use IlluminateContractsQueueShouldBeQueued;
    
    class RoomReservedEvent implements ShouldBeQueued {
    
    use InteractsWithQueue;
    
    public function __construct()
    {
        //
    }
    
    use IlluminateContractsQueueShouldBeQueued;

This interface tells Laravel that the event handler should be queued and not executed synchronously:

use IlluminateQueueInteractsWithQueue;

This trait allows us to interact with the queue to be able to do tasks, such as delete the job.

The waiting list command

For the fourth task, being placed on a waiting list, we'll need to create another command that would be called from inside the reservation controller. Once again, using Artisan, we can easily create the command and its corresponding event as follows:

$ php artisan make:commandPlaceOnWaitingListCommand
$ php artisan make:eventPlacedOnWaitinglist

Now, in our reservation controller, we would add the check for roomAvailability and then dispatch the PlaceOnWaitinglist command as follows:

public function store()
    {
    …
    …
        if ($roomAvailable) {
            $this->dispatch(
              new ReserveRoomCommand( $start_date, $end_date, $rooms)
            );
        } else {
            $this->dispatch(
              new PlaceOnWaitingListCommand($start_date, $end_date, $rooms)
            );
        }
    …

The queued commands

We can easily queue the commands by adding queued to the create command:

$ php artisan make:commandReserveRoomCommand -–handler --queued

This will use whichever queuing system is available, such as beanstalkd, and not immediately run the command. Instead, it will be placed in the queue and run later. We'll need to add an interface to the Command class:

IlluminateContractsQueueShouldBeQueued

In this case, the ReserveRoomCommand class will look like this:

<?php namespace MyCompanyCommands;

use MyCompanyCommandsCommand;

use IlluminateQueueSerializesModels;
use IlluminateQueueInteractsWithQueue;
use IlluminateContractsQueueShouldBeQueued;

class MyCommand extends Command implements ShouldBeQueued {

	use InteractsWithQueue, SerializesModels;

	/**
	 * Create a new command instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		//
	}

}

Here, we can see that the InteractsWithQueue and ShouldBeQueued classes have been included, and the ReserveRoomCommand class extends the command and implements the ShouldBeQueued class. Another interesting feature is SerializesModels. This will serialize any models, which are passed in, to be available later.

The console command

For the fifth task, let's create a console command, which will be executed very often:

$ php artisan make:consoleManageWaitinglist

This will create a command that can be executed from the Artisan command-line tool. If you have used Laravel 4, you may be familiar with this type of command. These commands are stored in the Console/Commands/ directory.

To let Laravel know about it, we will need to add it to app/Console/Kernel.php in the $commands array:

protected $commands = [
    'MyCompanyConsoleCommandsInspire',
    'MyCompanyConsoleCommandsManageWaitinglist',
    ];

The contents will look like this:

<?php namespace MyCompanyConsoleCommands;

use IlluminateConsoleCommand;
use SymfonyComponentConsoleInputInputOption;
use SymfonyComponentConsoleInputInputArgument;

class ManageWaitinglist extends Command {

    /**
    * The console command name.
    *
    * @var string
    */
    protected $name = 'command:name';

    /**
    * The console command description.
    *
    * @var string
    */
    protected $description = 'Command description.';

    /**
    * Create a new command instance.
    *
    * @return void
    */
    public function __construct()
    {
        parent::__construct();
    }

    /**
    * Execute the console command.
    *
    * @return mixed
    */
    public function fire()
    {
        //
    }

    /**
    * Get the console command arguments.
    *
    * @return array
    */
    protected function getArguments()
    {
        return [
          ['example', InputArgument::REQUIRED, 'An example argument.'],
        ];
    }

    /**
    * Get the console command options.
    *
    * @return array
    */
    protected function getOptions()
    {
        return [
          ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
        ];
    }
}

The $name attribute is what will be called from Artisan. For example, if we set the following:

protected $name = 'manage:waitinglist';

Then, by running the following command, we can manage the waiting list:

$ php artisan manage:waitinglist

The getArguments() and getOptions() methods are similar methods with the same signature, but have different uses.

The getArguments() method specifies an array of arguments that must be used to launch the command. The getOptions() methods are specified with and can be optional, repeated, and with the VALUE_NONE option, they can be simply used as flags.

We will write the command's main code inside the fire() method. If we want to dispatch a command from within this command, we'll add the DispatchesCommands trait to the class as follows:

    use DispatchesCommands;
    
    <?php namespace MyCompanyConsoleCommands;
    
    use IlluminateConsoleCommand;
    use IlluminateFoundationBusDispatchesCommands;
    use SymfonyComponentConsoleInputInputOption;
    use SymfonyComponentConsoleInputInputArgument;
    
    class ManageWaitinglist extends Command {
    
        use DispatchesCommands;
        
        /**
        * The console command name.
        * @var string
         */
        protected $name = 'manage:waitinglist';
        
        /**
        * The console command description.
        * @var string
        */
        protected $description = 'Manage the accommodation waiting list.';

        /**
        * Create a new command instance.
        *
        * @return void
        */
        public function __construct()
        {
            parent::__construct();
        }

        /**
        * Execute the console command.
        * @return mixed
        */
        public function fire()
        {
            // TODO: write business logic to manage waiting list
            if ($roomIsAvailableFor($user)) {
                $this->dispatch(new ReserveRoomCommand());
            }
        }

        /**
        * Get the console command arguments.
        * @return array
        */
        protected function getArguments()
        {
            return [];
        }

        /**
        * Get the console command options.
        * @return array
        */
        protected function getOptions()
        {
            return [];
        }
}

The command scheduler

Now, we will schedule this command to run every 10 minutes. Traditionally, this was done by creating a cron job to execute the Laravel console's command. Now, Laravel 5 provides a new mechanism to do this—the command scheduler.

The new artisan command is run as follows:

$ php artisan schedule:run

By simply adding this command to the cron, Laravel will automatically run all of the commands that are in the Kernel.php file.

The commands need to be added to the Schedule function as follows:

protected function schedule(Schedule $schedule)
    {
        $schedule->command('inspire')
             ->hourly();
        $schedule->command('manage:waitinglist')
            ->everyFiveMinutes();

    }

The inspire command is a sample command provided by Laravel to demonstrate the functionality. We will simply add our command. This will call the manage:waitinglist command every 5 minutes—it couldn't be much easier than that.

Now we need to modify the crontab file to have Artisan run the scheduler.

The crontab is a file that contains commands to be run at certain times. To modify this file, type the following command:

$ sudo crontab -e

We will use vi or whichever the assigned editor is to modify the cron table. Adding the following line will tell cron to run the scheduler every minute:

* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset