The task card shows a list of related tasks when a note is clicked (“opened”). If a task data is modified when it’s shown on the task card, it updates in real-time. Here I ran into an issue.
Every time a task data is modified, a Laravel event broadcasts the entire list of tasks to Echo, for updating the task list. Since the returned array directly replaces Vue’s reactive tasks array, it needs to be in the same format as the original array. Which means the same code needs to return data in both cases: for the original array that a controller function returns on page load, and for the array the Laravel event returns every time there’s a modification.
$tasks = Task::orderByRaw(
'is_done, due_time is null,
due_time,
created_at desc'
)
->select(
'id',
'name',
'due_time as dueTimeUtc',
'is_done as isDone'
)
->limit(20)
->get();
Even though the code to get data for the task list is relatively simple, keeping the same code in multiple places makes it very hard to change anything later. Every time there’s a change in the array structure, the change will have to made in all the places. This not only is redundant but also causes error that are hard to catch. Just forgetting to make changes in one place can have you looking into half the project files.
Imagine what happens with complex code.
So, there must be a way to use the same code from multiple places.
A few possible ways
With some help from my limited knowledge of Laravel, I instantly thought these ways:
-
Making an
app()->call()call inside the event. -
Passing
$tasksto the event in the::dispatch()call. -
Using a global helper to get
$tasksfrom both places. -
Using a model method to get
$tasksfrom both places.
#1 app()->call() inside event
This is a pretty straightforward way. The controller’s index() method will stay as is, and it’ll be called from inside the event. This way, the single source of data still is the controller method.
class TaskUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $tasks;
public function __construct()
{
$this->tasks = app()->call('App\Http\Controllers\TaskController@index');
}
}
#2 Parameter of ::dispatch()
In this case, whichever controller method is supposed to fire the event calls the controller’s index() method itself to obtain task data. And then it passes that data to the event.
For example, when a task’s data is modified, the update() method itself will get the array of tasks form index() and then simply pass it to TaskUpdated::dispatch() as a parameter:
public function index()
{
// Returns an array of tasks
}
public function update(Task $task)
{
// Code for updating the task
$tasks = $this->index();
TaskUpdated::dispatch( $tasks );
}
public function __construct($tasks)
{
$this->tasks = $tasks;
}
#3 Global helper function
Here, we move the source of data from index() to a global helper function, and then call the helper from both index() and the event.
public function index()
{
return tasksForTaskList();
}
public function __construct()
{
$this->tasks = tasksForTaskList();
}
#4 Model method
Almost the same as a global helper except it’s a model method.
So, which one?
The 4th way was instantly out of the question. Getting data on a specific number of tasks formatted in a specific way seemed too case-dependent for a model method. In fact, I didn’t even test if it worked. And later I realized I probably misunderstood how models work.
Using a global helper also didn’t seem right. It seemed unnecessary, and it removed a very important part from the controller and put it in an almost random place.
The other two ways seemed plausible, but it’d probably depend on conventions or good practices as to which should be used. (Personally, I was leaning toward way #2.)
Check out Mantle on GitHub here