loading...

Rocket — becoming a known developer… and building an app to help make it happen — Part 2

mattkingshott profile image Matt Kingshott 👨🏻‍💻 Originally published at itnext.io on ・7 min read

Rocket — becoming a known (Laravel) developer… and building an application to help make it happen — Part 2

In this series, we will be reviewing the steps that we as developers need to take in order to create a suitable web presence and build an audience that we can communicate ideas, projects and commercial offerings with.

In addition to theory articles discussing marketing, presentation and methods of engagement, we will also be building Rocket — a Laravel application that will allow you and other developers to create a personal site and grow an audience through social media and article publishing… let’s dive in!

NOTICE : I had intended to record a set of videos to accompany Rocket’s development. Unfortunately, my internet is too unreliable to regularly upload GB video files. As a result, I’m switching to articles with code review, which if I’m honest, is more in my comfort zone anyway. That said, if you haven’t seen the already uploaded videos, I suggest checking them out to get up to speed.

Today’s agenda

In this article, we’ll be reviewing the steps required to implement the basics of subscriptions within the Rocket app. Note that some of the code will be using placeholder strings until such time as we add UI and front-end components such as flash message notifications.

Step #1 — Setup the database

Let’s begin by creating a subscriber table, model and database factory. As we would expect, Laravel has our back with a single Artisan command:

php artisan make:model Subscriber -mf

Now, let’s set up the migration. We could elect to store many items of data about our subscribers… but that’s invasive and creepy. It’s also a surefire way to reduce signups. We developers want to provide an email and nothing else. So, we’ll add an email field and include a timestamp for email verification:

Schema::create('subscribers', function (Blueprint $table) {
    $table->id();
    $table->string('email', 255)->unique();
    $table->timestamp('verified\_at')->nullable();
    $table->timestamps();
});

Let’s also take a moment to configure our database factory class too:

$factory->define(Subscriber::class, function (Faker $faker) {
    return [
        'email' => $faker->unique()->safeEmail,
        'verified\_at' => now(),
    ];
});

Nice. Now, it’s on to the Subscriber model. To make our lives easier, we’ll set up casting for the verified_at field, so it gets converted to a Carbon instance.

Since most / all of our subscriber database queries will depend on whether a subscriber is verified or not, it makes sense to add some helper scopes:

use Illuminate\Database\Eloquent\Builder;

class Subscriber extends Model
{
    /\*\*
     \* The attributes that should be cast to native types.
     \*
     \*/
    protected $casts = [
        'verified\_at' => 'datetime',
    ];

    /\*\*
     \* Restrict query results to subscribers that are verified.
     \*
     \*/
    public function scopeVerified(Builder $query) : Builder
    {
        return $query->whereNotNull('verified\_at');
    }

    /\*\*
     \* Restrict query results to subscribers that are not verified.
     \*
     \*/
    public function scopeUnverified(Builder $query) : Builder
    {
        return $query->whereNull('verified\_at');
    }
}

With these scopes in place, we can now easily apply them whenever we want to perform a database query e.g. Subscriber::verified()->get()

Step #2 — Registration

Now that our database is all set up to handle subscribers, let’s add a means for developers to register their interest. We’ll begin with a route:

Route::post('/subscribe', 'SubscriptionController@store');

Next, we’ll need a controller to handle the route:

php artisan make:controller SubscriptionController

Now, let’s add the basic controller code needed to store a subscription:

use App\Models\Subscriber;
use App\Requests\Subscriber\StoreRequest;

class SubscriptionController extends Controller
{
    /\*\*
     \* Store a new subscriber.
     \*
     \*/
    public function store(StoreRequest $request) : string
    {
        $subscriber = Subscriber::create($request->validated());

        return "Thanks! You're subscribed. Please verify";
    }
}

Rocket will use Form Requests to handle all user input, so let’s create one:

php artisan make:request StoreRequest

Perfect. Now, since we’ll be accepting an email address from the user, we need to make sure to include it within the rules array:

use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    /\*\*
     \* Get the validation rules that apply to the request.
     \*
     \*/
    public function rules() : array
    {
        return [
            'email' => 'bail|required|email|unique:subscribers',
        ];
    }
}

Excellent. We’ve now got some simple validation in place to ensure that the user’s email is formatted properly and does not already exist in the database.

Back in the controller, we can now call the validated method on the request object to retrieve ONLY the validated fields / data.

By using the validated method, we no longer run the risk of mass-assignment.

We then use the validated data to create a new Subscriber record and send a simple thank you message back to the user. Sweet!

Step #3 — Unsubscribing

Your content won’t always be to everyone’s taste or interest, so we have to provide a means for users to unsubscribe. Let’s add a route for that:

Route::get('/unsubscribe/{subscriber}', 'SubscriptionController@destroy')->name('unsubscribe');

We’ve assigned this route a name because we want to be able to sign all URLs to it. By signing a URL, we can allow people to unsubscribe by clicking a link. However, if they try to modify the link (to unsubscribe someone else), then Laravel will reject the request and redirect them to an error page.

Now, let’s set up a destroy method on the SubscriptionController to handle the unsubscribe process. Thanks to route model binding, it’s a cinch:

use App\Models\Subscriber;

class SubscriptionController extends Controller
{
    /\*\*
     \* Delete a subscriber.
     \*
     \*/
    public function destroy(Subscriber $subscriber) : string
    {
        $subscriber->delete();

        return 'Got it! You have been unsubscribed';
    }
}

Step #4 — Controller middleware

We’re almost done with the controller (for now), but there’s one more thing we need to add to it… some extra middleware:

class SubscriptionController extends Controller
{
    /\*\*
     \* Constructor.
     \*
     \*/
    public function \_\_construct()
    {
        $this->middleware('signed')->only('destroy');
        $this->middleware('throttle:3,1')->only('store');
    }
}

So, what are we doing here? Firstly, we’re assigning the signed middleware to the destroy method. This ensures that Laravel enforces the requirement that the URL to unsubscribe a user must be a signed URL.

Secondly, we’re assigning the throttle middleware to the store route. This prevents a user from registering more than three times a minute. This is preventative more than anything, but it can help with bots.

Tip : You can also assign the middleware directly to your routes if you prefer to keep the middleware out of your controllers.

Step #5 — Keeping things tidy

There is one scenario we haven’t tackled yet. What if someone registers their email address, but never bothers to verify it? Rather than hold on to an email address that we will never use, we should purge it after a few days.

Let’s create a console command to do that for us:

php artisan make:command PurgeUnverifiedCommand

Next, let’s schedule it to run every day at midnight:

$schedule->command('subscriber:purge')->daily();

Now, let’s use our unverified model scope to delete the appropriate records:

use App\Models\Subscriber;
use Illuminate\Console\Command;

class PurgeUnverifiedCommand extends Command
{
    /\*\*
     \* The name and signature of the console command.
     \*
     \*/
    protected $signature = 'subscriber:purge';

    /\*\*
     \* Execute the console command.
     \*
     \*/
    public function handle() : void
    {
        Subscriber::unverified()
                  ->where("created\_at", "<", now()->subDays(3))
                  ->delete();

        $this->info('The unverified subscribers have been removed');
    }
}

Excellent. So, every day at midnight, Laravel will execute a database query to delete all subscribers with a created_at date older than three days AND with a verified_at value equal to null.

Step #6 — Testing that it all works

As good developers, it’s important that we write tests to confirm that our code in fact does what it is supposed to do. Let’s begin by creating some simple unit tests to verify that our model scopes apply the expected restrictions:

/\*\* [@test](http://twitter.com/test) \*/
public function it\_can\_scope\_to\_verified\_subscribers() : void
{
    $f = factory(Subscriber::class, 1);
    $a = $f->create(['verified\_at' => now()])->first();
    $b = $f->create(['verified\_at' => null])->first();

    $this->assertCount(1, Subscriber::verified()->get());

    $this->assertEquals(
        $a->id, Subscriber::verified()->first()->id
    );
}

/\*\* [@test](http://twitter.com/test) \*/
public function it\_can\_scope\_to\_unverified\_subscribers() : void
{
    $f = factory(Subscriber::class, 1);
    $a = $f->create(['verified\_at' => now()])->first();
    $b = $f->create(['verified\_at' => null])->first();

    $this->assertCount(1, Subscriber::unverified()->get());

    $this->assertEquals(
        $b->id, Subscriber::unverified()->first()->id
    );
}

Next, let’s confirm that our console command deletes the correct records:

/\*\* [@test](http://twitter.com/test) \*/
public function it\_can\_purge\_unverified\_subscribers() : void
{
    $d1 = now()->subDays(1);
    $d4 = now()->subDays(4);
    $d5 = now()->subDays(5);

    $fields\_1 = ['verified\_at' => now()];
    $fields\_2 = ['verified\_at' => null, 'created\_at' => $d1];
    $fields\_3 = ['verified\_at' => null, 'created\_at' => $d4];
    $fields\_4 = ['verified\_at' => null, 'created\_at' => $d5];

    $a = factory(Subscriber::class, 1)->create($fields\_1)->first();
    $b = factory(Subscriber::class, 1)->create($fields\_2)->first();
    $c = factory(Subscriber::class, 1)->create($fields\_3)->first();
    $d = factory(Subscriber::class, 1)->create($fields\_4)->first();

    $this->artisan('subscriber:purge');

    $this->assertCount(1, Subscriber::verified()->get());
    $this->assertCount(1, Subscriber::unverified()->get());

    $this->assertEquals(
        $b->id, Subscriber::unverified()->first()->id
    );
}

Finally, let’s add some feature tests to confirm that both user registration and unsubscribing works as advertised:

/\*\* [@test](http://twitter.com/test) \*/
public function a\_subscriber\_can\_unsubscribe\_themselves() : void
{
    $subscriber = factory(Subscriber::class, 1)->create()->first();

    $url = URL::signedRoute('unsubscribe', [
        'subscriber' => $subscriber->id,
    ]);

    $this->get($url)
         ->assertStatus(200)
         ->assertSee('Got it! You have been unsubscribed');

    $this->assertCount(0, Subscriber::get());
}

/\*\* [@test](http://twitter.com/test) \*/
public function a\_subscriber\_can\_subscribe\_themselves() : void
{
    $this->post('/subscribe', ['email' => '[john@example.com](mailto:john@example.com)'])
         ->assertStatus(200)
         ->assertSee("Thanks! [john@example.com](mailto:john@example.com) is now subscribed");

    $this->assertCount(1, Subscriber::get());

    $this->assertNull(Subscriber::first()->verified\_at);

    $this->assertEquals(
        '[john@example.com](mailto:john@example.com)', Subscriber::first()->email
    );
}

Wonderful! We now have the foundation we need for subscriptions.

Wrapping up

Now that we can register and unsubscribe users, we can focus on fleshing out the features by adding a slick user interface and adding verification emails.

That’s our next job in part #3. I hope you’re excited for it!

To ensure you’re notified when it comes out, why not go ahead and follow me here on Medium, or better yet, on Twitter, where I’ll also be posting additional updates as well as links to new articles.

Thanks, and have a great day! 😎


Posted on Sep 9 '19 by:

mattkingshott profile

Matt Kingshott 👨🏻‍💻

@mattkingshott

Founder. Developer. Writer. Lunatic. Created Pulse, IodineJS, Axiom, and more. #PHP #Laravel #Vue #TailwindCSS

Discussion

markdown guide