Build A Link Sharing Website With Laravel

Build A Link Sharing Website With Laravel

Building your own applications, even on a simple or small scale, is a great way to build your skills. This Link Sharing Website tutorial using Laravel will help us to do just that. We’re going to cover a lot of ground as you can see in the table of contents listed below. We start right at the very basics of choosing a name for your project, and build through all the steps required to get a working application by the time you hit the last step.

I like to put things into a step by step sequence of events. This outline will provide us with each step to take, in order, when creating a basic website in Laravel.


#Choose a project name

We will go soup to nuts here, with everything built on top of Laravel Homestead. It’s the best way to have a reliable and flexible development platform right on your own machine. If you’d like to follow along, do install Laravel Homestead first. We’ll call our project angleslash, and we’ll want to be able to simpy visit http://angleslash.dev in our browser, and have everything work.


#Install Homestead

Getting Homestead running is a tutorial in itself. Just read the documentation to get up and running, or visit our article here at Vegibit that talks about configuring Homestead.

We’ll need to configure our Homestead.yaml file to add our new site to the homestead server. This is pretty easy, just make sure not to use any tab characters in the file, otherwise vagrant will bug out on you. Our current Homestead.yaml file has these entries in the sites section.

sites:
    - map: homestead.app
      to: /home/vagrant/Code/laravel/public
    - map: larabook.dev
      to: /home/vagrant/Code/larabook/public
    - map: angleslash.dev
      to: /home/vagrant/Code/angleslash/public

note: If you already have homestead running, you can still simply edit the Homestead.yaml, then run: vagrant provision, and you’ll be good to go. In addition, you’ll need to set your hosts file to add an entry for your new project. For windows this is located at C:\Windows\System32\drivers\etc\hosts Our current hosts file looks like this:

#homestead
192.168.10.10   homestead.app
192.168.10.10   larabook.dev
192.168.10.10   angleslash.dev

You can see that this homestead server has 3 different projects running all at one time. We have a base homestead.app project for various testing, we installed the larabook application from Jeffrey Way’s Laracast series, and now we have our new social link sharing website, angleslash.dev. Homestead really does provide a nice and convenient way to work on multiple projects at once. If you’re developing multiple websites, it is a must.


#Create the angleslash app

Now that we have everything ready to go, we can start building the project. The first thing we’ll need to do is ssh into our server.
vagrant ssh

Once we are logged in to our server, we can easily create a new project with Composer.
vagrant@homestead:~/Code composer create-project laravel/laravel angleslash --prefer-dist


#Set the Application Namespace

We can easily set the namespace for our application with Artisan, so let’s do so.
vagrant@homestead:~/Code/angleslash$ php artisan app:name angleslash

You should see the result, Application namespace set!


#Open the project in PHP Storm

Yes it’s a paid solution, but it’s the best. It also helps to really see how all of the code of a framework ties together. So if you don’t have it yet, go buy yourself a copy! We’ll add barryvdh’s ide helper since it is so fantastic to help us with code completion.

vagrant@homestead:~/Code/angleslash$ composer require barryvdh/laravel-ide-helper

Let’s add a few more packages that may be helpful while we are at it.

vagrant@homestead:~/Code/angleslash$ composer require guzzlehttp/guzzle
vagrant@homestead:~/Code/angleslash$ composer require illuminate/html
vagrant@homestead:~/Code/angleslash$ composer require laravel/cashier

Notice that since we simply used Composer to add the requirements, we don’t even have to manually edit or futz around with the composer.json file. All of that gets taken care of for us by composer which is nice.


#Configure The app.php file

Once the dependencies are set up the first place we’ll usually head to is the app.php file found in the config directory of the root namespace. In here we can configure some of the basic settings that any application will need.

First, we’ll configure our base url.

'url' => 'http://angleslash.dev',

Next, we will populate the providers array with some new entries.

'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
'Illuminate\Html\HtmlServiceProvider',
'Laravel\Cashier\CashierServiceProvider',

Finally we will configure the aliases array.

'Html'      => 'Illuminate\Html\HtmlFacade',
'Form'      => 'Illuminate\Html\FormFacade',

This will allow us to continue to use the original HTML and Form helpers from earlier versions of Laravel. For good measure, lets now do a composer update, then generate the docs for the ide helper. By completing the ide-helper:generate command, our code editor will now have intellisense like auto complete for Laravel. It’s a fantastic feature.

vagrant@homestead:~/Code/angleslash$ composer update
vagrant@homestead:~/Code/angleslash$ php artisan ide-helper:generate

We’ve done a fair amount of configuration, and we have a pretty nice base install of the Laravel framework going. Just to confirm, we’ll visit http://angleslash.dev/, and yes, we see the friendly splash page indicating that we have a successful installation of Laravel to work with.
Create Your Own Social Link Sharing Website With Laravel

In fact, the tutorial up until this point is a great workflow checklist no matter what type of project you might be creating. Jeez, I might need to bookmark this post!


#Configure Environment Settings

In the root namespace, we can open the .env file and set some information. Really the only thing we are worried about right now in this development environment is to set the database name. We’ll use angleslash as the database name for this project. All other options in the .env file will be left at defaults for now.

DB_HOST=localhost
DB_DATABASE=angleslash
DB_USERNAME=homestead
DB_PASSWORD=secret

#Create The Database

Our application is going to need a database to work with. Since we have multiple projects going on our homestead server, we’ll need to create a new database to work with. Recall that the user for mysql in homestead is homestead and the password is secret. Lets create this database from the mysql terminal now.

vagrant@homestead:~$ mysql -u homestead -p
Enter password: secret

mysql> create database angleslash;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;

+--------------------+
| Database           |
+--------------------+
| information_schema |
| angleslash         |
| homestead          |
| mysql              |
| performance_schema |
+--------------------+

5 rows in set (0.00 sec)

mysql>exit

Perfect! With that, we now have a database ready to use, and we can move on.


#Run Your First PHP Test

From the terminal, we can run the example test that comes with Laravel. This is the command to run it.

vagrant@homestead:~/Code/angleslash$ vendor/bin/phpunit

PHPUnit 4.6.7 by Sebastian Bergmann and contributors.

Configuration read from /home/vagrant/Code/angleslash/phpunit.xml

.

Time: 4.61 seconds, Memory: 13.50Mb

OK (1 test, 1 assertion)

The test above simply ran the ExampleTest.php in the tests directory. Let’s take a look at the code involved.

<?php

class ExampleTest extends TestCase {

	/**
	 * A basic functional test example.
	 *
	 * @return void
	 */
	public function testBasicExample()
	{
		$response = $this->call('GET', '/');

		$this->assertEquals(200, $response->getStatusCode());
	}

}

It just makes sure that visiting the homepage gives back a 200 ok response.


#Create The Routes File

The routes file in Laravel gives us a high level overview of how our application will function. We can see all of the endpoints that need to respond to requests, as well as which controllers will be in use. Here is the routes file we have for our link sharing website. After we quickly review the routes file, we’ll then build out the link sharing website according to how we have our routes file configured.

Routes File

<?php

// We use the Laravel built in Auth
Route::controllers([
    'auth' => 'Auth\AuthController',
    'password' => 'Auth\PasswordController',
]);
//----------------------------------------//

// Display users, subs and front page
Route::get('/', 'PostController@frontpage');
Route::get('u/{user}', 'UserController@show');
Route::get('r/{sub}', 'SubController@show');
//----------------------------------------//

// Checking if the user is signed in (using AJAX)
Route::get('authcheck', function () {
    return json_encode(Auth::check());
});
//----------------------------------------//

// Creating and storing a new sub
Route::get('sub/new', [
    'middleware' => 'auth',
    'uses' => 'SubController@displayform'
]);

Route::post('sub/new', [
    'middleware' => 'auth',
    'uses' => 'SubController@storesub'
]);
//----------------------------------------//

// Creating and storing a new post / link
Route::get('post/new', [
    'middleware' => 'auth',
    'uses' => 'PostController@displayform',
]);

Route::post('post/new', [
    'middleware' => 'auth',
    'uses' => 'PostController@storepost'
]);
//----------------------------------------//

// Voting on posts / links
Route::post('vote', [
    'middleware' => 'auth',
    'uses' => 'VoteController@vote'
]);

#Create Controllers With Artisan

We’ll next use Artisan to generate the controllers we’ll need in our application. I’m trying to become as familiar as possible with Artisan, since automatic code generation sounds like a great deal to me. First off, let’s just take a quick look at all of the code generation options available to us. We can do this by piping the output of php artisan to the grep command while searching for make.

vagrant@homestead:~/Code/angleslash$ php artisan | grep make

make
 make:command          Create a new command class
 make:console          Create a new Artisan command
 make:controller       Create a new resource controller class
 make:event            Create a new event class
 make:middleware       Create a new middleware class
 make:migration        Create a new migration file
 make:model            Create a new Eloquent model class
 make:provider         Create a new service provider class
 make:request          Create a new form request class

This is great! Artisan provides the ability to create new command classes, resources, events, middleware, migrations, service providers, and also the Form Requests which people have been raving about. Let’s quickly create all of the controllers we’ll need for our application.

vagrant@homestead:~/Code/angleslash$ php artisan make:controller UserController
Controller created successfully.
vagrant@homestead:~/Code/angleslash$ php artisan make:controller PostController
Controller created successfully.
vagrant@homestead:~/Code/angleslash$ php artisan make:controller SubController
Controller created successfully.
vagrant@homestead:~/Code/angleslash$ php artisan make:controller VoteController
Controller created successfully.

All of our controllers are now created. They are currently empty, but we are making progress with creating the skeleton of the application. Let us now create the Models.


#Create Models With Artisan

We are going to use the built in User Model that ships with Laravel, so we don’t need to create that. We will modify it slightly, and add some Eloquent Relationships to it a little later. We do need some models to handle our Posts, Post Votes, and Subs. Let’s create those now again using Artisan. Subs are just categories, like a subreddit.

vagrant@homestead:~/Code/angleslash$ php artisan make:model Post
Model created successfully.
Created Migration: 2015_06_01_175305_create_posts_table
vagrant@homestead:~/Code/angleslash$ php artisan make:model PostVote
Model created successfully.
Created Migration: 2015_06_01_175322_create_post_votes_table
vagrant@homestead:~/Code/angleslash$ php artisan make:model Sub
Model created successfully.
Created Migration: 2015_06_01_175337_create_subs_table

vagrant@homestead:~/Code/angleslash$ php artisan make:migration foreign_keys
Created Migration: 2015_06_01_181451_foreign_keys

Note: When we create each Model, Laravel automatically creates a migration for us. Since we have our migration files set up, let’s populate our migrations with the Schema we will need to support our basic link sharing website.


#Create Supporting Migrations

create_posts_table.php

<?php

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

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('title', 100);
            $table->string('url', 2083);
            $table->integer('sub_id')->unsigned();
            $table->integer('user_id')->unsigned();
        });
    }

    public function down()
    {
        Schema::drop('posts');
    }
}

create_postvotes_table.php

<?php

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

class CreatePostVotesTable extends Migration
{
    public function up()
    {
        Schema::create('post_votes', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('type', 4);
            $table->integer('user_id')->unsigned();
            $table->integer('post_id')->unsigned();
        });
    }

    public function down()
    {
        Schema::drop('post_votes');
    }
}

create_subs_table.php

<?php

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

class CreateSubsTable extends Migration
{
    public function up()
    {
        Schema::create('subs', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('name', 20);
            $table->integer('owner_id')->unsigned();
        });
    }

    public function down()
    {
        Schema::drop('subs');
    }
}

create_foreign_keys.php

<?php

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

class ForeignKeys extends Migration
{
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreign('sub_id')->references('id')->on('subs')
                ->onDelete('restrict')
                ->onUpdate('restrict');
        });
        Schema::table('posts', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('users')
                ->onDelete('restrict')
                ->onUpdate('restrict');
        });
        Schema::table('subs', function (Blueprint $table) {
            $table->foreign('owner_id')->references('id')->on('users')
                ->onDelete('restrict')
                ->onUpdate('restrict');
        });
        Schema::table('post_votes', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('users')
                ->onDelete('restrict')
                ->onUpdate('restrict');
        });
        Schema::table('post_votes', function (Blueprint $table) {
            $table->foreign('post_id')->references('id')->on('posts')
                ->onDelete('restrict')
                ->onUpdate('restrict');
        });
    }

    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeign('posts_sub_id_foreign');
        });
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeign('posts_user_id_foreign');
        });
        Schema::table('subs', function (Blueprint $table) {
            $table->dropForeign('subs_owner_id_foreign');
        });
        Schema::table('post_votes', function (Blueprint $table) {
            $table->dropForeign('post_votes_user_id_foreign');
        });
        Schema::table('post_votes', function (Blueprint $table) {
            $table->dropForeign('post_votes_post_id_foreign');
        });
    }
}

With our migration files now created, let’s run the migrations with Artisan to create the tables in the database we’ll need.

vagrant@homestead:~/Code/angleslash$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2015_06_01_175305_create_posts_table
Migrated: 2015_06_01_175322_create_post_votes_table
Migrated: 2015_06_01_175337_create_subs_table
Migrated: 2015_06_01_181451_foreign_keys

If we log in to mysql and issue the show tables command on the database in question, we’ll see that our migrations did a great job of creating all the tables we’ll need.

mysql> show tables;

+----------------------+
| Tables_in_angleslash |
+----------------------+
| migrations           |
| password_resets      |
| post_votes           |
| posts                |
| subs                 |
| users                |
+----------------------+
6 rows in set (0.00 sec)

#Implement Our Models

We were able to use Artisan to create the boilerplate for our models. We still need to fill in the logic on our own. Just like filling in the code for Schema, this requires some thought by the developer. We will make use Laravel hasMany and Laravel belongsTo relationships. This is how we might do that.

User Model

<?php namespace angleslash;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{

    use Authenticatable, CanResetPassword;

    protected $table = 'users';
    protected $fillable = ['name', 'email', 'password'];
    protected $hidden = ['password', 'remember_token'];

    public function posts()
    {
        return $this->hasMany('angleslash\Post');
    }

    public function subs()
    {
        return $this->hasMany('angleslash\Sub');
    }

    public function postvotes()
    {
        return $this->hasMany('angleslash\PostVote');
    }
}

Sub Model

<?php namespace angleslash;

use Illuminate\Database\Eloquent\Model;

class Sub extends Model
{
    protected $table = 'subs';
    protected $fillable = ['name', 'owner_id'];

    public function posts()
    {
        return $this->hasMany('angleslash\Post');
    }

    public function owner()
    {
        return $this->belongsTo('angleslash\User');
    }
}

Post Model

<?php namespace angleslash;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';
    protected $fillable = ['title', 'url', 'sub_id', 'user_id'];

    public function user()
    {
        return $this->belongsTo('angleslash\User');
    }

    public function sub()
    {
        return $this->belongsTo('angleslash\Sub');
    }

    public function votes()
    {
        return $this->hasMany('angleslash\PostVote');
    }
}

Post Vote Model

<?php namespace angleslash;

use Illuminate\Database\Eloquent\Model;

class PostVote extends Model
{
    protected $table = 'post_votes';
    protected $fillable = ['type', 'user_id', 'post_id'];

    public function post()
    {
        return $this->belongsTo('angleslash\Post');
    }

    public function user()
    {
        return $this->belongsTo('angleslash\User');
    }
}

#Implement Our Controllers

Just like our Models, we need to still fill in the logic of our controllers. We have to account for the User, Sub, Post, and Vote Controllers. Let’s do that here.

User Contoller

<?php namespace angleslash\Http\Controllers;

use angleslash\Http\Requests;
use angleslash\Http\Controllers\Controller;

use Illuminate\Http\Request;
use angleslash\User;

class UserController extends Controller
{
    public function show($name)
    {
        $user = User::where('name', $name)->firstOrFail();

        return view('profile')
            ->with('title', $user->name)
            ->with('user', $user);
    }
}

Sub Contoller

<?php namespace angleslash\Http\Controllers;

use angleslash\Http\Requests;
use angleslash\Http\Controllers\Controller;
use angleslash\Http\Requests\SubFormRequest;
use angleslash\Sub;

use Illuminate\Http\Request;

class SubController extends Controller
{
    public function show($name)
    {
        $sub = Sub::where('name', $name)->firstOrFail();

        return view('sub')
            ->with('sub', $sub->name)
            ->with('posts', $sub->posts()->paginate(15));
    }

    public function displayform()
    {
        return view('forms.createsub')
            ->with('title', 'Create Sub');
    }

    public function storesub(SubFormRequest $request)
    {
        $sub = new Sub;
        $sub->name = $request->get('name');
        $sub->owner_id = \Auth::id();
        $sub->save();

        return \Redirect::to('r/' . $request->get('name'));
    }
}

Post Controller

<?php namespace angleslash\Http\Controllers;

use angleslash\Http\Requests;
use angleslash\Http\Controllers\Controller;
use angleslash\Sub;
use angleslash\Post;
use angleslash\PostVote;
use angleslash\Http\Requests\PostFormRequest;

use Illuminate\Http\Request;

class PostController extends Controller
{
    public function show($sub, $postId)
    {
        $sub = Sub::where('name', $sub)->firstOrFail();
        $post = Post::find($postId);

        return view('post')
            ->with('title', $post->title)
            ->with('sub', $post->sub->name)
            ->with('post', $post);
    }

    public function frontpage()
    {
        return view('sub')
            ->with('title', 'Front Page')
            ->with('posts', Post::paginate(15));
    }

    public function displayform()
    {
        return view('forms.submit')
            ->with('title', 'New Post');
    }

    public function storepost(PostFormRequest $request)
    {
        $post = new Post;

        $post->title = $request->get('title');
        $post->url = $request->get('url');
        $post->sub_id = Sub::where('name', $request->get('sub'))->first()->id;
        $post->user_id = \Auth::id();
        $post->save();

        return \Redirect::to('/');
    }
}

Vote Controller

<?php namespace angleslash\Http\Controllers;

use angleslash\Http\Requests;
use angleslash\Http\Controllers\Controller;
use angleslash\PostVote;

use Illuminate\Http\Request;

class VoteController extends Controller
{
    public function vote()
    {
        $class = \Input::get('class');
        $postId = \Input::get('postId');
        $previousVote = PostVote::where('user_id', \Auth::id())->where('post_id', $postId)->first();
        $isUpvote = str_contains($class, 'up');

        // If there is a vote by the same user on the same post
        if (!is_null($previousVote)) {
            if ($isUpvote) {
                if ($previousVote->type === 'up') {
                    // Cancel out previous upvote
                    $previousVote->delete();
                } else {
                    $previousVote->update(['type' => 'up']);
                }
            } else {
                if ($previousVote->type === 'down') {
                    // Cancel out previous downvote
                    $previousVote->delete();
                } else {
                    $previousVote->update(['type' => 'down']);
                }
            }
        } else {

            // Create a new vote
            PostVote::create([
                'type' => $isUpvote ? 'up' : 'down',
                'user_id' => \Auth::id(),
                'post_id' => $postId
            ]);
        }
    }
}

#Create Form Requests

We’re making some serious progress here. In the above section when creating our controllers, you may have noticed that we used type hinting in the store methods for both the Sub and Post controllers. This is a really slick way to complete validation with the awesome new feature in Laravel, Form Requests. The way it works is, we can simply create a new Request using Artisan, and then fill in the validation rules for the request. Once this is done, we can simply pass in the request to the associated method where we need to validate data. Let’s create our Form Request for both the Sub and Post now.

vagrant@homestead:~/Code/angleslash$ php artisan make:request SubFormRequest
Request created successfully.
vagrant@homestead:~/Code/angleslash$ php artisan make:request PostFormRequest
Request created successfully.

SubFormRequest

<?php namespace angleslash\Http\Requests;

use angleslash\Http\Requests\Request;

class SubFormRequest extends Request
{

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|min:3|max:20|alpha_dash|unique:subs'
        ];
    }
}

PostFormRequest

<?php namespace angleslash\Http\Requests;

use angleslash\Http\Requests\Request;

class PostFormRequest extends Request
{

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|max:100',
            'url' => 'required|max:2083|active_url',
            'sub' => 'required|exists:subs,name'
        ];
    }
}

All we had to do was provide the rules for our validation. Form Requests are a really fantastic new feature of Laravel.


#Create Associated Forms

We need to create two new forms for our application. One for creating a new Sub, or category, and another for creating a new Post or Link. Since we are using Laravel’s built in forms for Registering and Logging in, we don’t even need to worry about those. If you want to dig into creating forms, you can also check out this laravel forms tutorial. Here is the code for both of our forms.

createsub.blade.php

@extends('default')

@section('content')

    {!! Form::open(['url' => 'sub/new']) !!}

    <div class="col-sm-8 col-sm-offset-2">
        <p class="text-center alert alert-info"><span class="glyphicon glyphicon-ok"></span> <b>Create</b> a new
            sub</p>
    </div>

    <div class="col-sm-8 col-sm-offset-2">
        @if($errors->first('name'))
            {!! $errors->first('name', '<div class="alert alert-warning"><b>:message</b></div>') !!}
            <?php $name = 'has-error'; ?>
        @endif

        <div class="form-group {!! $name or '' !!}">
            {!! Form::text('name', $value = null, ['class' => 'form-control input-lg', 'placeholder' => 'Enter the name of the sub, then click Go']) !!}
        </div>

        {!! Form::submit('Go!', ['class' => 'btn btn-lg btn-block btn-info']) !!}
    </div>

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

@endsection

submit.blade.php

@extends('default')

@section('content')

    {!! Form::open(['url' => 'post/new']) !!}

    <div class="col-sm-8 col-sm-offset-2">
        <p class="text-center alert alert-info"><span class="glyphicon glyphicon-ok"></span> <b>Submit</b> a new
            link</p>
    </div>

    <div class="col-sm-8 col-sm-offset-2">
        @foreach ($errors->all(':message') as $message)
            <div class="alert alert-warning"><b>{!! $message !!}</b></div>
            @if(str_contains($message,'title'))
                <?php $title = 'has-error'; ?>
            @endif
            @if(str_contains($message,'url'))
                <?php $url = 'has-error'; ?>
            @endif
            @if(str_contains($message,'sub'))
                <?php $sub = 'has-error'; ?>
            @endif
        @endforeach
        <div class="form-group {!! $title or '' !!}">
            {!! Form::text('title', $value = null, ['class' => 'form-control input-lg', 'placeholder' => 'Enter the title here']) !!}
        </div>

        <div class="form-group {!! $url or '' !!}">
            {!! Form::text('url', $value = null, ['class' => 'form-control input-lg', 'placeholder' => 'http://example.com']) !!}
        </div>

        <div class="form-group {!! $sub or '' !!}">
            {!! Form::text('sub', $value = null, ['class' => 'form-control input-lg', 'placeholder' => 'Name of sub here']) !!}
        </div>

        {!! Form::submit('Go!', ['class' => 'btn btn-lg btn-block btn-info']) !!}
    </div>

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

Note: We did add a small bit of logic to include the ability to add an error class to whatever form field fails to validate. For example, if you enter an invalid URL and submit the form, when it comes back to the form with errors, not only will the application spell out the problem for you, but the URL form field will have a red outline around it via the Bootstrap has-error class.


#Create A Master Layout

Blade is growing on me in a major way. The more you use it, the more you love it. In this master layout for our link sharing website application, we’ll include all of the asset resources we need to support us. Here is our master layout file.

default.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>

    <!-- Include Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
    <script src="/js/main.js"></script>
    <link rel="stylesheet" href="/css/app.css">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ $sub or 'Welcome' }} | Angleslash</title>
</head>

<body class="container">
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle Navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="{{ url('/') }}"><Angleslash></a>
        </div>

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">

            </ul>

            <ul class="nav navbar-nav navbar-right">
                @if (Auth::guest())
                    <li><a href="{{ url('/auth/login') }}">Login</a></li>
                    <li><a href="{{ url('/auth/register') }}">Register</a></li>
                @else
                    <li><a href="/post/new">Submit Link</a></li>
                    <li><a href="/sub/new">Create Sub</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                           aria-expanded="false">{{ Auth::user()->name }} <span class="caret"></span></a>
                        <ul class="dropdown-menu" role="menu">
                            <li><a href="{{ url('/auth/logout') }}">Logout</a></li>
                        </ul>
                    </li>
                @endif
            </ul>
        </div>
    </div>
</nav>
@yield('content')

<div id="modal" class="modal fade">

    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">×</span></button>
                <h4 class="modal-title">Boo Yeah!</h4>
            </div>
            <div class="modal-body">
                <h2>Join Angleslash</h2>

                <p class="lead">In order to vote, you must be signed in!</p>

                <div class="lead text-center"><a href="{{ url('/signup') }}">Register</a> or <a
                            href="{{ url('/signin') }}">Login</a></div>
            </div>
            <div class="modal-footer">
                <Angleslash>
            </div>
        </div>
        <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
</div>
<!-- /.modal -->

</body>
</html>

#Create Sub, Post, and User Profile Blade Views

sub.blade.php

@extends('default')

@section('content')
    <div>
        @if ($posts->count() > 0)
            @foreach ($posts as $post)
                @include('snippets.post', ['post' => $post])
            @endforeach
        @else
            <p>Nobody has submmitted a post yet, looks like you will be the first!</p>
        @endif
    </div>
@endsection

post.blade.php

@extends('default')

@section('content')
    <div class="container">

        @include('snippets.post', $post)

    </div>
@endsection

profile.blade.php

@extends('default')

@section('content')

    <!--The user's posts-->
    <div class="col-lg-8">
        @foreach($user->posts()->paginate(15) as $post)
            @include('snippets.post', array('post' => $post))
        @endforeach

    </div>

    <!--The user profile-->
    <div class="col-lg-4">
        <div class="alert bg-success">
            <h3>User Profile</h3>

            <p>{{ $user->name }} has been a user for {{ Helper::timeAgo($user->created_at, 'user') }} and has
                submitted {{ $user->posts->count() }} posts so far</p>
        </div>
    </div>

@endsection

snippets/post.blade.php

<?php
$vote = angleslash\PostVote::where('user_id', Auth::id())->where('post_id', $post->id)->first();
$type = null;

if (!is_null($vote)) {
    $type = $vote->type;
}
?>
<div class="panel panel-default {{ $post->id }}">
    <div class="panel-body bg-info">
        <div class="col-xs-1">
            <span class="lead glyphicon glyphicon-menu-up vote {{ $type === 'up' ? 'active' : '' }}"
                  aria-hidden="true"></span>
        </div>
        <div class="col-xs-11">
            <a href="{{ $post->url }}">
                <h3>{{{ $post->title }}}</h3>
            </a>
        </div>
        <div class="col-xs-1">
            <span class="lead glyphicon glyphicon-menu-down vote {{ $type === 'down' ? 'active' : '' }}"
                  aria-hidden="true"></span>
        </div>
        <div class="col-xs-11">
            <div class="votes">{{ $post->votes()->count() }} votes so far</div>
        </div>
        <div class="col-xs-12">
            <span class="pull-right">
            submitted {{ Helper::timeAgo($post->created_at) }} ago by
            <a href="/u/{{ $post->user->name }}">
                {{ $post->user->name }}
            </a>
            to
            <a href="/r/{{ $post->sub->name }}">
                {{ $post->sub->name }}
            </a>
            </span>
        </div>
    </div>

</div>

#Implement a Helper Class

You may have noticed in our view file we referenced a method like so Helper::timeAgo(). In order to make that available to us in our views, we’ll need to include this class in our application. This is how we did it here. In the app folder, where the models live, simply add a file named Helper.php. Place this code inside of it.

Helper.php

<?php

class Helper {
    
    public static function timeAgo($datetime, $type = 'post')
    {
        $now    = new DateTime;
        $ago    = new DateTime($datetime);
        $diff   = $now->diff($ago);
        $result = '';

        $diff->w = floor($diff->d / 7);
        $diff->d -= $diff->w * 7;

        $string = [
            'y' => 'year',
            'm' => 'month',
            'w' => 'week',
            'd' => 'day',
            'h' => 'hour',
            'i' => 'minute',
            's' => 'second',
        ];
        foreach ($string as $k => &$v)
        {
            if ($diff->$k)
            {
                $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
            }
            else
            {
                unset($string[$k]);
            }
        }

        $string = array_slice($string, 0, 1);
        if ($type === 'post')
        {
            $result = $string ? implode(', ', $string) . ' ago' : 'just now';
        }
        else if ($type === 'user')
        {
            $result = $string ? implode(', ', $string) . '' : 'a nanosecond';
        }

        return $result;
    }
}

Modify the autoload block in composer.json to include this file.

	"autoload": {
		"classmap": [
			"database"
		],
		"psr-4": {
			"angleslash\\": "app/"
		},
        "files": [
            "app/Helper.php"
        ]
	},

Finally, run composer dump.

vagrant@homestead:~/Code/angleslash$ composer dump
Generating autoload files

The helper is now available to us in our views.


#Implement Post Votes With Ajax

We need a bit of JavaScript to provide for the ability to vote on Posts via ajax. Here is the code we use to accomplish this.

main.js

jQuery(document).ready(function($){

    // Check if a user is signed in
    var isSignedIn = $.ajax({
        url: '/authcheck',
        method: 'get',
        async: false
    }).responseText === 'true' ? true : false;

    $.ajaxSetup({
        headers: {
            'X-CSRF-Token': jQuery('meta[name="csrf-token"]').attr('content')
        }
    });

    // handle votes
    jQuery('.vote').click(function () {
        if (isSignedIn) {
            var postId = $(this).closest('.panel').attr('class').split(' ')[2];
            $(this).toggleClass('active');

            if ($(this).hasClass('glyphicon-menu-up')) {
                jQuery('.post-' + postId + ' .vote.glyphicon-menu-down').removeClass('active');
            } else if ($(this).hasClass('glyphicon-menu-down')) {
                jQuery('.post-' + postId + ' .vote.glyphicon-menu-up').removeClass('active');
            }

            $.ajax({
                url: '/vote',
                method: 'post',
                data: {
                    'class': $(this).attr('class'),
                    'postId': postId
                }
            });
        } else {
            jQuery('#modal').modal();
        }
    });
});

#Set redirectTo for Laravel Auth System

Laravel’s built in registration, login, and password reset features are really fantastic. By default, these features redirect you to /home which might be the dashboard of your application. In our case, we simply use / as the “home” of our application. To set this, simply open both the AuthController.php and PasswordController.php and add this one line.

protected $redirectTo = '/';

#Screenshots of Angleslash

We’ve done a lot of work up until this point. We are now ready to take our link sharing website application for a spin. We’ll make use of a handful of excellent links to test our link sharing website application. Let’s try http://laravel.com, https://leanpub.com/easyecommerce, https://laravel-news.com/, and http://www.easylaravelbook.com/ Let’s check it out!

Registering A New User

Registering A New User

Creating a New Sub

Creating A New Sub

Submitting A New Link

Submit A New Link

Viewing A User Profile

Viewing A Profile

Cool Modal Effect for Logged Out Users

If a guest or non logged in user tries to click the vote buttons, either up or down, they will trigger a modal that will request them to either register for a new account, or log in with their existing account.
Modal Trigger For Non Logged In Users

Viewing The Front Page of Angleslash

Laravel link sharing website

Build A Link Sharing Website With Laravel Conclusion

This has been a really fun tutorial that put the rubber to the road on a sample Laravel application. The goal was to cover an example Laravel workflow in a soup to nuts fashion, starting with Homestead, and covering a wide range of topics including choosing a project name, creating a new application with Composer, setting a namespace with Artisan, using PHP Storm for code editing, configuring a base url, configuring environment settings, running a sample PHP Unit test, creating a routes file, using Artisan to generate Controllers, Models, Migrations, and Form Requests, as well as completing very basic styling with Bootstrap.

Leave a Reply