How can we add favourites to our Laravel 5.4 app?

Part of our commissioning the back-end series.

Love and Laravel 5.4

Introduction

Please note that this article refers to Laravel 5.4.

Have you asked yourself how can we as an organisation recruit the same type of person again and again and again and get away with it ?

We all know the ethical considerations when an organisation deploys unethical practices to create a socially immobile and homogenous workforce (think of HBO’s Westworld containing androids of the same ethnicity for example).

Sometimes in web development we need to use these types of practices in order to prevent repetition.

Thankfully, in Laravel there is a tool you can use to favourite a post, product or person if need be. As you may know we like to think unconventionally here at Ormrepo because this is how you can grow your business over the long term.

A few months ago we created a lettings application called Casarentals and this contains a series of lettings and house sales.

As part of our remit we were asked to add a favourite lettings to enable users to have their own customised lists of properties.

We will demonstrate how to add this feature in to our existing application.

Please refer to the final code which is located here:

Migration

We will start by creating a new database migration so that we are able to store these favourites within a table.

At the command prompt type:

php artisan make:migration create_favourites_table

We will create a new table that will contain a letting_id and a user_id both of these will be indexed.

Add the following database schema to the code listed below:

database/migrations/create_favourites_table

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFavouritesTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create(‘favourites’, function(Blueprint $table)
 {
 $table->increments(‘id’);
 $table->primary([‘user_id’, ‘letting_id’]);
 $table->integer(‘user_id’)->index();
 $table->integer(‘letting_id’)->index();
 $table->timestamps();
 });
 }

/**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::drop(‘favourites’);
 }
}

The next step is to migrate the tables so that they are active.

php artisan migrate

Show page

The next thing we need to think about is where are we going to show the favourites button. The best place we can place this for the time being is on the show page. 
 
If you take a look at the snippet below you will notice that we have included the favourites button within a table surrounded by a vue js accordion.

Favourites in an accordion

Unfortunately, we wont be showing you how to build the accordion in this tutorial this is because it is out of the scope of this tutorial.

Lets get back to adding the favourite to the show page.

Take a look at the code below it shows the favourite partial.

resources/views/admin/lettings/show.blade.php

@extends (‘app’)

@section(‘meta-title’, ‘Buy or Rent on the Web’)

@section (‘content’)

<div id=”app”>
 <! — Using the Alert component →
 <alert class=”Alert — Error”></alert>
 </div><! — /#app →

<div class=”columns-container”>
 <div class=”properties-container is — padded-left-50">
 <div class=”search-headings”>
 <h1 class=”index-heading”> {{ $letting->type }}</h1>
 </div><! — /.search-headings →
 <div class=”carousel”>
 <div class=”slider”>
 <img src=”{{ asset (‘images/featured/’ . $letting->id . ‘/living-room.png’) }}” alt=”The living room within the house”>
 <img src=”{{ asset (‘images/featured/’ . $letting->id .’/kitchen.png’) }}” alt=”The kitchen within the house”>
 <img src=”{{ asset(‘images/featured/’ . $letting->id .’/bedroom.png’) }}” alt=”The bedroom within the house”>

</div>
 </div><! — /.carousel →
 </div><! — /.properties-container →

<aside>
 <div class=”accord-container”>
 <div id=”root” class=”container”>
 <tabs>
 <tab name=”Description” :selected=”true”>
 <h3 class=”is — padded-tb10">Letting Description:</h3>
 <div class=”col-md-4">
 <h6>From:</h6>
 <h6>Fixtures:</h6>
 <h6>Lease:</h6>
 <h6>Nr Station:</h6>
 <h6>Favourite:</h6>
 </div><! — /.col-md-4 →

<div class=”col-md-4">
 <p class=”is — padded-t5">{!! $letting->date !!}</p>
 <p class=”is — padded-t5">{!! $letting->furnished !!}</p>
 <p class=”is — padded-t5">{!! $letting->lease !!}</p>
 <p class=”is — padded-t5">{!! $letting->station_summary !!}</p>
 <p class=”is — padded-t5">@include(‘partials.favourite-button’)</p>
 </div><! — /.col-md-4 →
 </tab>

Notice that we have added an include where we have added the partials button.

We need to create this partial, underneath resources/views create a new directory called partials. 
Within the partials directory create a new file called favourite-button.blade.php.

Take a look at this file listed below we will explain how this works in the next step:

partials/favourite-button.blade.php

```
 @if ($favourited = in_array($letting->id, $favourites))
 {!! Form::open([‘method’ => ‘DELETE’, ‘route’ => [‘favourites.destroy’, $letting->id]]) !!}

@else
 {!! Form::open([‘route’ => ‘favourites.store’]) !!}
 {!! Form::hidden(‘letting_id’, $letting->id) !!}
@endif

<button type=”submit” class=”btn-naked”>
 <i class=”fa fa-heart {{ $favourited ? ‘favourited’ : ‘not-favourited’ }}” {{ Auth::guest() ? ‘disabled’ : ‘’ }} aria-hidden=”true”></i>
</button>

{!! Form::close() !!}

We will open up a form that will point to the favourites.destroy route and we will pass the letting id through here.
This will reside in an if statement, the logic as as follows.

If the favourite is clicked it will pass the letting id through as a hidden field and it will go to the favourites store page. If it is clicked again it will delete the favourite.

Lets add the submit button for the button and we will check to see if it is favourited or not using the bootstrap heart icon from the twitter bootstrap library.

Web (Routes file)

We need to place a declaration within the routes file to inform the routes file how we are going to manage the favourites button.

web.php

Route::resource(‘favourites’, ‘FavouritesController’, [‘only’ => ‘store’]);
Route::delete(‘favourites/{lettingId}’, [‘as’ => ‘favourites.destroy’, ‘uses’ => ‘FavouritesController@destroy’]);

As you can see from the declaration above we have a favourites controller whose responsibility is to store and delete the favourites on the back end.

We need to create our favourites controller, go back to your command prompt and type the following:

php artisan make:controller FavouritesController

Lets take a look at the FavouritesController methods below:

app\http\controllers\favouritescontroller.php

<?php

namespace App\Http\Controllers;

use App\Letting;
use Illuminate\Support\Facades\Input;
use App\Exceptions\FavouriteNotFoundException;

class FavouritesController extends Controller
{
 /**
 * FavouritesController constructor.
 * Initialise the auth middleware used to
 * secure the Favourites routes.
 *
 */
 public function __construct()
 {

$this->middleware(‘auth’);

}

/**
 * Grab the letting id and toggle the favourites button
 * find the letting object and record the activity.
 * @return \Illuminate\Http\RedirectResponse
 * @throws FavouriteNotFoundException
 */
 public function store()
 {
 try
 {

$user = auth()->user();

$lettingId = Input::get(‘letting_id’);

$user->favourites()->toggle($lettingId);

$letting = Letting::find($lettingId);

$user->recordActivity(‘favourited’, $letting);

} catch (FavouriteNotFoundException $e)

{
 throw new FavouriteNotFoundException($e->getMessage());
 }
 return redirect()->back();

}

/**
 * Grab the lettingId and detach it from the likes and then find the Letting
 * and record the activity.
 *
 * @param $lettingId
 * @return \Illuminate\Http\RedirectResponse
 * @throws FavouriteNotFoundException
 */
 public function destroy($lettingId)
 {

try
 {

$user = auth()->user();

$user->favourites()->detach($lettingId);

$letting = Letting::find($lettingId);

$user->recordActivity(‘unfavourited’, $letting);

} catch (FavouriteNotFoundException $e)

{
 throw new FavouriteNotFoundException($e->getMessage());
 }
 return redirect()->back();

}
}

We need to add 3 new methods to our controller.

Constructor

The first is a constructor this will designate our controller as an auth controller. This means you need to be logged in to add or remove a favourite.

Store method

The second method is the store method, this will fetch the letting_id. The letting_id is passed to the user favourites and the favourite is toggled. Once this is complete it then performs a search for the letting and the activity is recorded and it is redirected back.

Destroy method
 The logged in user is verified and the favourite is detached and a search is carried out to find the letting id.
 
You may have noticed that we have a relationship listed between our user and the favourites. The thing is that we have not declared this relationship to Laravel’s IOC container.

The way we can relate this in the UK is having a relationship with a boyfriend or girlfriend and not getting married.

In the UK the state only recognises your unmarried relationship when it comes to some of its dealings with you i.e. benefits etc

Whilst in Laravel you need to declare your relationship otherwise you will receive an error out of the IOC container.

You need to be explicit in Laravel and declare all relationships whilst in real life there is a fudge where the UK state only recognises certain aspects of unmarried relationships.

Our next step is to define the favourites relationship on our User model.

Relationships

Add the belongs to many to the user model as demonstrated in the code below.

app\user.php

namespace App;

use Exception;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Auth;
use HttpOz\Roles\Traits\HasRole;
use HttpOz\Roles\Contracts\HasRole as HasRoleContract;

class User extends Authenticatable implements HasRoleContract
{
 use Notifiable, HasRole;
 /**
 * The database table used by the model.
 *
 * @var string
 */
 protected $table = ‘users’;

/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
 protected $fillable = [
 ‘username’, ‘email’ ,’name’, ‘is_admin’, ‘password’,’password_confirmation’,
 ];
 
 /**
 * Relationship
 *
 * A user can have many favourites.
 *
 * @return \Illuminate\Database\Eloquent\Relations\HasMany
 */
 public function favourites()
 {
 return $this->belongsToMany(‘App\Letting’, ‘favourites’)->withTimestamps();
 }

This relationship is a belongstomany relationship and it means that a user can have many favourites on the lettings model.
 
In your set up make sure that you have set up an inverse relationship between your user and your lettings model.

This will ensure that your letting knows about the user before you click on the favourites button.

Recap

We have performed the following so far
 
 1. Created a database migration
 2. Amended the show page
 3. Created a favourites partial view
 4. Amended the routes web file
 5. Added a favourites controller
 6. Added relationships on the models.
 
 
 The next stage is to create a view composer.
 
 ### What is a view composer ?
 
 These are special class methods that are called during the view rendering process and they are bound at run time. This means that once a view composer is created it can be attached to your views seamlessly.
 
We are going to create a new service provider called ComposerService provider.

At the command prompt type:

php artisan make:provider ComposerServiceProvider

Before we populate out ComposerServiceProvider we are going to create a Favourites Composer.

Go to app\Http and create a new directory called Composers.

Within the above mentioned composers directory add a new file called FavouritesComposer.php.

Populate the FavouritesComposer.php with the following:

app\http\composers\FavouritesComposer.php

<?php

namespace App\Http\Composers;

use App\Favourites;

use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class FavouritesComposer
{

/**
 * Grab the favourites with the appropiate letting id and
 * pass them to the view.
 * @param View $view
 */
 public function compose(View $view)
 {
 $favourites = DB::table(‘favourites’)->whereUserId(Auth::user()->id)->pluck(‘letting_id’);

$favourites = $favourites->toArray();

$view->with(‘favourites’, $favourites);
 }

}

The composer method contains the favourite variable this plucks a letting id from the favourites table we made earlier and it dumps it in to an array. The array is piped to the favourites variable and this is contained in the view.
 
 Now we have our favouritescomposer.php we can add this to our ComposerServiceProvider.
 
 
 Within the boot method add the composeFavourites protected function within here. This will boot the favourites composer method and it will pass the favourites to the index and show view.
 
 This is demonstrated further in the code below:
 
app\http\providers\ComposerServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

/**
 * Class ViewComposerServiceProvider
 * @package App\Providers
 */
 
class ComposerServiceProvider extends ServiceProvider
{
 /**
 * Bootstrap the application services.
 *
 * @return void
 */
 public function boot()
 {
 $this->composeFavourites();
 
 }

/**
 * Register the application services.
 *
 * @return void
 */
 public function register()
 {
 //
 }

/**
 *
 * Boot the Favourites View Composer
 *
 */
 protected function composeFavourites()
 {
 view()->composer([‘admin.lettings.index’, ‘admin.lettings.show’], ‘App\Http\Composers\FavouritesComposer’);
 }

}

Finally we need to add the ComposerServiceProvider in to our app.php so that the IOC container knows it is there. Add it to the providers array.

config/app.php

‘providers’ => [

/*
 * Laravel Framework Service Providers…
 */
 Illuminate\Auth\AuthServiceProvider::class,
 Illuminate\Broadcasting\BroadcastServiceProvider::class,
 Illuminate\Bus\BusServiceProvider::class,
 Illuminate\Cache\CacheServiceProvider::class,
 Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
 Illuminate\Cookie\CookieServiceProvider::class,
 Illuminate\Database\DatabaseServiceProvider::class,
 Illuminate\Encryption\EncryptionServiceProvider::class,
 Illuminate\Filesystem\FilesystemServiceProvider::class,
 Illuminate\Foundation\Providers\FoundationServiceProvider::class,
 Illuminate\Hashing\HashServiceProvider::class,
 Illuminate\Mail\MailServiceProvider::class,
 Illuminate\Notifications\NotificationServiceProvider::class,
 Illuminate\Pagination\PaginationServiceProvider::class,
 Illuminate\Pipeline\PipelineServiceProvider::class,
 Illuminate\Queue\QueueServiceProvider::class,
 Illuminate\Redis\RedisServiceProvider::class,
 Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
 Illuminate\Session\SessionServiceProvider::class,
 Illuminate\Translation\TranslationServiceProvider::class,
 Illuminate\Validation\ValidationServiceProvider::class,
 Illuminate\View\ViewServiceProvider::class,

/*
 * Package Service Providers…
 */
 Laravel\Tinker\TinkerServiceProvider::class,
 Collective\Html\HtmlServiceProvider::class,
 Roumen\Feed\FeedServiceProvider::class,
 Laravel\Scout\ScoutServiceProvider::class,
 Spatie\Newsletter\NewsletterServiceProvider::class,
 Propaganistas\LaravelPhone\LaravelPhoneServiceProvider::class,
 NotificationChannels\OneSignal\OneSignalServiceProvider::class,
 AdamWathan\EloquentOAuthL5\EloquentOAuthServiceProvider::class,
 HttpOz\Roles\RolesServiceProvider::class,
 SquareBoat\Sneaker\SneakerServiceProvider::class,

/*
 * Application Service Providers…
 */
 App\Providers\AppServiceProvider::class,
 App\Providers\AuthServiceProvider::class,
 App\Providers\BroadcastServiceProvider::class,
 App\Providers\EventServiceProvider::class,
 App\Providers\RouteServiceProvider::class,
 App\Providers\ComposerServiceProvider::class,

],

We have added the view composer to the providers array and we have successfully completed our Laravel favourites tutorial.

As you can see Laravel deals with explicit relationships as opposed to fudged relationships in real life. We hope that you have learnt how to build favourites in to your workflow.

Over the next few weeks we will be releasing additional articles from this series and we hope that you have learnt something from our tutorials.

Stay tuned…….