|

Laravel Subscription System Tutorial

Laravel Subscription System Tutorial

Many popular websites allow you to subscribe to a particular topic or thread. In forum type applications, you often see the ability to subscribe to certain topics, and once you are subscribed you can get notifications via email or on the website itself. In this tutorial, we will build out a thread subscription system. It is fairly involved as it needs the back end system to power it, in addition to a reactive subscribe button powered by VueJS on the front end. In addition, there will be a notifications area in the upper right of the website similar to what you might see on Facebook or Reddit.


A Thread Can Be Subscribed To

So what’s the very first thing we can do? Well, we’ll start off with a test in our existing ThreadTest class. Just as a quick start, here is the pseudo code.

  • Given there is a thread
  • Given an authenticated user
  • When that user subscribes to the thread
  • Then the user should be able to fetch all subscribed threads

Running the test shows us what we need to do next, which is to create a new subscribe() method.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_thread_can_be_subscribed_to
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 1.27 seconds, Memory: 8.00MB

There was 1 error:

1) Tests\Unit\ThreadTest::test_a_thread_can_be_subscribed_to
BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::subscribe()

We can create this new subscribe() method on the Thread.php model like so. In fact, we’ll fast forward a bit for the sake of brevity and get build out a few methods we are going to need. We don’t need to show each failing test here. We’ll need a subscribe() method, a subscriptions() method, and a new ThreadSubscriptions class along with a new migration.

vagrant@homestead:~/Code/forumio$ php artisan make:model ThreadSubscription -m
Model created successfully.
Created Migration: 2018_02_20_192313_create_thread_subscriptions_table

The fields we will need in our new database table are defined by the migration we just created as follows:

We will also add these two new methods to our Thread.php model class.

Finally, we’ll also add a basic relationship to the ThreadSubscription class. We are saying “This ThreadSubscription belongsTo a Thread”.

Now we are getting a passing status on this first test.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_thread_can_be_subscribed_to
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 831 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

A Thread Can Be Unsubscribed From

Of course a user will also be able to unsubscribe from any particular thread that they have subscribed to. We can add a test for that as well. So what is the test case?

  • Given there is a thread
  • Given a user is subscribed to that thread
  • When the unsubscribe feature is triggered
  • Then the thread should have no subscriptions

Here is the actual test code for this feature.

In order to make this test pass, we’ll need a few things. First up is a new unsubscribe() method on the Thread model.

With that simple addition, the test passes.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_thread_can_be_unsubscribed_from
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 840 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

Now our test environment will run migrations automatically on each test run, but let’s also run the migrations manually so that we can test ourselves in the browser and see the subscribe feature working as we move forward.

vagrant@homestead:~/Code/forumio$ php artisan migrate
Migrating: 2018_02_20_192313_create_thread_subscriptions_table
Migrated:  2018_02_20_192313_create_thread_subscriptions_table

We don’t yet have the front end in place to test subscribing to a thread, but we can take this for a test run in tinker. Let’s see. We’ll get a recent thread and store it in a variable.

>>> $thread = App\Thread::latest()->first();
=> App\Thread {#814
     id: 59,
     user_id: 53,
     channel_id: 1,
     title: "The Weather is Beautiful Outside",
     body: "Spring is in the air and temperatures outside are warming up.  Enjoy the beautiful day!",
     created_at: "2018-02-20 18:06:12",
     updated_at: "2018-02-20 18:06:12",
     replies_count: 3,
     creator: App\User {#822
       id: 53,
       name: "Nikola Tesla",
       created_at: "2018-01-23 18:07:43",
       updated_at: "2018-01-23 18:07:43",
     },
     channel: App\Channel {#812
       id: 1,
       name: "repellat",
       slug: "repellat",
       created_at: "2017-12-21 16:32:19",
       updated_at: "2017-12-21 16:32:19",
     },
   }

Now, we will subscribe a user to that thread. Here we are saying that the user with the id of 52 is subscribing to the given thread.

>>> $thread->subscribe(52);
=> App\Thread {#814
     id: 59,
     user_id: 53,
     channel_id: 1,
     title: "The Weather is Beautiful Outside",
     body: "Spring is in the air and temperatures outside are warming up.  Enjoy the beautiful day!",
     created_at: "2018-02-20 18:06:12",
     updated_at: "2018-02-20 18:06:12",
     replies_count: 3,
     creator: App\User {#822
       id: 53,
       name: "Nikola Tesla",
       created_at: "2018-01-23 18:07:43",
       updated_at: "2018-01-23 18:07:43",
     },
     channel: App\Channel {#812
       id: 1,
       name: "repellat",
       slug: "repellat",
       created_at: "2017-12-21 16:32:19",
       updated_at: "2017-12-21 16:32:19",
     },
   }

We can check in the new database table that we had created and sure enough, we see a new subscription there!
new subscription in the database

We should also be able to unsubscribe just as easily. Let’s see.

>>> $thread->unsubscribe(52);
=> null

Now when we check in the database, that subscription we saw just a minute ago is now gone. It looks like the subscribe() and unsubscribe() methods are working great!
the user is now unsubscribed


Implementing A Higher Level Feature Test

We started at the Unit level to get the most basic functionality set up for subscribing and unsubscribing to a thread. We will now move up the chain and work at the Feature Test level to further support the code.
subscribe to new thread feature test

Fleshing out the first test at the Feature level looks like this.

We’re going to need a coupld of routes to support this new feature. We’ll add an endpoint we can post to for a new subscription as well as an endpoint to delete a subscription.


A New ThreadSubscriptionsController Class

We set up a few routes that reference the ThreadSubscriptionsController class, but we don’t have that class yet. Let’s create it.

vagrant@homestead:~/Code/forumio$ php artisan make:controller ThreadSubscriptionsController
Controller created successfully.

We’ll also add a happy little store() method on our new controller.

Running our test shows that we’ve done enough to get this test to pass.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_a_user_can_subscribe_to_threads
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 1.3 seconds, Memory: 10.00MB

OK (1 test, 1 assertion)

Adding A Subscribe Button To The User Interface

The basics of the back end are now fleshed out and based on the tests we have created appear to be working. Now we want to set up a button on the front end that response appropriately to user input. In other words, the button needs to maintain state, similar to how the favorite vuejs child component button did. With that in mind, we can create a new SubscribeButton.vue component.
new SubscribeButton vue component

We will populate SubscribeButton.vue like so.

In Thread.vue we need to reference the new component in a couple of spots.

Finally, we’ll render the new button in our threads/show.blade.php file like this.

Voila!
subscribe component example

There is actually a fair amount of behavior happening in the above snippets and it follows the same concepts that we have been covering regarding working with Vue components. Trying this new subscribe button in the browser now shows us that clicking the button does produce a new subscription in the database. In addition, note the state of the button changes properly to indicate you have subscribed. Cool!
button click subscription added


Is the thread subscribed to?

We should add a custom attribute getter to the Thread model to tell us whether a thread is currently subscribed to or not. We learned a bit about how these work in a prior tutorial. For this iteration, the code would look like this in Thread.php.

We would also configure the $appends property like so.

If we simply return the JSON formatted data from the show() method in the ThreadsController, we see that we have access to that needed information.
custom getter appends property in laravel

This is the data that gets sent through when we reference

in threads/show.blade.php.


Deleting A Subscription

Everything is working for clicking the button and seeing a new subscription populate in the database. All we need to do is add a destroy() method to the ThreadSubscriptionsController class to delete a subscription as well.

Now clicking the button adds a new subscription. In addition, clicking a second time will remove the subscription. Looks good!
add or remove subscription


Laravel Subscription System Tutorial Summary

This tutorial covered the basics of setting up the needed code for a subscription system for users of the application. When a user clicks a subscribe button in the UI, that user creates a new entry in the database to indicate a successful subscription. Clicking the button again removes the subscription for that user. In the next tutorial, we’ll see how to set up notifications for subscribed users.

|