Click to share! ⬇️

How To Set Up Form Submission In Laravel

In our little games app, we don’t yet have a way to add new games to the database using the app itself. We are only able to insert new data by using the MySql console directly, or via phpMyAdmin. Let’s change that up and start looking at how we can add the ability to render a form so that we can submit new games. We’ll need a new route and controller method to display the form, as well as a new route and controller method to store the data into the database. Let’s set this up now.


Add games/create route and GamesController@create method

By convention, when you want to display a form, a GET request is sent to games/create. If you’re dealing with tasks it would be tasks/create. If you’re dealing with posts it would be something like posts/create. The associated controller method would be something like GamesController@create. Ok, we need a route for games/create and we need a method in the controller for GamesController@create. Let’s try to set those up.


routes/web.php

<?php

Route::get('games', 'GamesController@index');

Route::get('games/{id}', 'GamesController@show');

Route::get('games/create', 'GamesController@create'); 

app/Http/Controllers/GamesController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Game;

class GamesController extends Controller
{
    public function index()
    {
        $games = Game::all();
        return view('games.index', ['games' => $games]);
    }
    
    public function show(Game $id)
    {
        return view('games.show', ['game' => $id]);
    }
    
    public function create()
    {
        return 'it works';
    }
}

Test out the route

With the new route and new controller method in place, let’s just quickly test that route in the browser. When we visit http://54.dev/games/create, we notice a problem.

Sorry, the page you are looking for could not be found.

2/2
NotFoundHttpException in Handler.php line 131:
No query results for model [App\Game].

Uh oh, that’s not great. What happened? Well it turns out the order of your routes matters in the routes file. It is a process of checking for matching routes from the top down, and as soon as a match is found, the route is processed. With the way our routes file is structured now, by visiting http://54.dev/games/create, we are actually triggering the Route::get(‘games/{id}’, ‘GamesController@show’); route and this is not what we want. All we need to to is re arrange the routes file like so, and all should be good.

<?php

Route::get('games', 'GamesController@index');

Route::get('games/create', 'GamesController@create');

Route::get('games/{id}', 'GamesController@show');

When we visit the route now, we get the nice text of ‘it works’ so we know the route and controller method we want are working. With this sorted out, we can update the create() method to render a view file for us so we can see the form.

    public function create()
    {
        return view('games.create');
    }

Create a new view of create.blade.php and add a Form

Within resources/views/games go ahead and add a view file named create.blade.php. We are going to create a form that allows us to fill out the Game Title, Game Publisher, Game Release Date, as well as upload an image for the game. There are a fair amount of gotchas when creating a form like this so we’ll just show the markup and then go over a few important bullet points.


resources/views/games/create.blade.php

@extends('layouts.master')

@section('content')

    <h2>Add a game</h2>

    <form method="post" action="/games" enctype="multipart/form-data">
        {{ csrf_field() }}
        <div class="form-group row">
            <label for="titleid" class="col-sm-3 col-form-label">Game Title</label>
            <div class="col-sm-9">
                <input name="title" type="text" class="form-control" id="titleid" placeholder="Game Title">
            </div>
        </div>
        <div class="form-group row">
            <label for="publisherid" class="col-sm-3 col-form-label">Game Publisher</label>
            <div class="col-sm-9">
                <input name="publisher" type="text" class="form-control" id="publisherid"
                       placeholder="Game Publisher">
            </div>
        </div>
        <div class="form-group row">
            <label for="releasedateid" class="col-sm-3 col-form-label">Release Date</label>
            <div class="col-sm-9">
                <input name="releasedate" type="text" class="form-control" id="releasedateid"
                       placeholder="Release Date">
            </div>
        </div>
        <div class="form-group row">
            <label for="gameimageid" class="col-sm-3 col-form-label">Game Image</label>
            <div class="col-sm-9">
                <input name="image" type="file" id="gameimageid" class="custom-file-input">
                <span style="margin-left: 15px; width: 480px;" class="custom-file-control"></span>
            </div>
        </div>
        <div class="form-group row">
            <div class="offset-sm-3 col-sm-9">
                <button type="submit" class="btn btn-primary">Submit Game</button>
            </div>
        </div>
    </form>

@endsection

Visiting http://54.dev/games/create shows us a pretty good looking form for submitting a new game.
create-blade-php form to submit data

Let’s review some important points about the new form.

  • The method attribute must be set to post: Every form is going to have an attribute named method. This specifies the type of HTTP request you will make with the form. When trying to add an item to the server, you must use a POST request. Since that is exactly what we are trying to do, we set the method attribute to POST.
  • The action attribute is set to /games: The other attribute that we must pay attention to in forms is the action attribute. This tells us where do we want to send the post request to. In keeping with convention, we make a post request to /games
  • The enctype attribute is set to multipart/form-data: Since we are including the ability to upload an image, we need to set the enctype attribute to multipart/form-data.
  • csrf_field is required: To protect against cross site request forgery, you must include this field just after the opening form tag like you see above. If this is not included, you will run into token mismatch errors.
  • each input has a unique name: Input tags in forms must have a name attribute with a unique value set. This is how we identify which data corresponds to which input when processing in Laravel or PHP on the server side.
  • button type of submit: In order to submit the form, you’re going to need a button with it’s type attribute set to submit!

Add post /games route and GamesController@store method

In order to actually process the data we enter into our form, we need the associated route and controller method to do so. We can update our routes file like this.

<?php

Route::get('games', 'GamesController@index');

Route::get('games/create', 'GamesController@create');

Route::get('games/{id}', 'GamesController@show');

Route::post('games', 'GamesController@store');

Getting data from the form submission

We are not going to try and save any data just yet. We just want to see what we have to work with when we submit the form. Recall that our form has four inputs with the names of title, publisher, releasedate, and image. Let’s see how to inspect these when we submit the form. Let’s populate the store() method on our GamesController like this for a quick second.

use App\Game;

class GamesController extends Controller
{
    public function index()
    {
        $games = Game::all();
        return view('games.index', ['games' => $games]);
    }
    
    public function show(Game $id)
    {
        return view('games.show', ['game' => $id]);
    }
    
    public function create()
    {
        return view('games.create');
    }
    
    public function store()
    {
        var_dump(request('title'));
        var_dump(request('publisher'));
        var_dump(request('releasedate'));
        var_dump(request('image'));
    }
}

Let’s go ahead and submit our form now to see what we get. Note that we did choose an image, but it does not update visually in the form simply because that is beyond the scope of this tutorial. The image will in fact be passed via the form however.
submitting a form with file upload

The result of dumping our request data looks like this.
inpsecting request data of laravel form submission

This shows that we are definitely getting all the data we want. We can see all form fields were successfully passed through the post request, including an image file which is an object of type Illuminate\Http\UploadedFile. Fantastic.

We now want to be able to save the title, publisher, releasedate, and image path to the database. In our current games table, we have fields for the title, publisher, and releasedate, but nothing for the image path. Hmm. Looks like we might need to adjust our migration file and re run the migrations. Let’s add this line to our original migration file.

<?php

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

class CreateGamesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('games', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('publisher');
            $table->integer('releasedate');
            $table->string('image');
            $table->timestamps();
        });
    }
    
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('games');
    }
}

We are now going to run php artisan migrate:refresh, which will reset and re-run all migrations. Don’t ever do this on a database for which you want to preserve data, because this basically deletes everything and re builds the tables. All data will be lost. For our little tutorial, this is no problem, but this is something to be aware of.
php artisan migrate refresh


Store The Game In The Database

With our games table now updated to include a path for an image file, we are ready to store a game in the database. Here is how we can update the store() method to do that for us.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Game;

class GamesController extends Controller
{
    public function index()
    {
        $games = Game::all();
        return view('games.index', ['games' => $games]);
    }
    
    public function show(Game $id)
    {
        return view('games.show', ['game' => $id]);
    }
    
    public function create()
    {
        return view('games.create');
    }
    
    public function store()
    {
        $game = new Game;
        
        $game->title = request('title');
        $game->publisher = request('publisher');
        $game->releasedate = request('releasedate');
        $game->image = request()->file('image')->store('public/images');
        $game->save();
    }
}

What happens in this store() method is pretty straight forward. We are able to use the request() helper function to access the data we want from the form. One of these kids is doing his own thing however. Take a look at the line which has this: $game->image = request()->file(‘image’)->store(‘public/images’); There are two things happening at once here. Laravel is accepting the file from our form submission, and storing it in the application at storage/app/public/images. At the same time, the path for the image is stored in $game->image so we fetch the image source when we want to display it to the browser later on. Let’s use tinker to see if everything is in the database like we expect.

new game is added via form success

Yes! It looks like it worked great, and we can see we now have an image path for the uploaded file as well.


How To Display Stored Images

We are going to need to update our show.blade.php view so we can make use of our newly uploaded image file. Let’s see what we’ll need to do.

Remove this highlighted line

@extends('layouts.master')

@section('content')

    <div class="card" style="width: 270px;margin: 5px">
        <img class="card-img-top" src="/{{ $game->title }}.png" alt="Card image cap">
        <div class="card-block">
            <h3 class="card-title">{{ $game->title }}</h3>
            <p class="card-text">{{ $game->title }} is published by {{ $game->publisher }}</p>
            <a href="/games" class="btn btn-primary">List Games</a>
        </div>
    </div>

@endsection

And replace like so

@extends('layouts.master')

@section('content')

    <div class="card" style="width: 270px;margin: 5px">
        <img class="card-img-top" src="{{ Storage::url($game->image)  }}" alt="Card image cap">
        <div class="card-block">
            <h3 class="card-title">{{ $game->title }}</h3>
            <p class="card-text">{{ $game->title }} is published by {{ $game->publisher }}</p>
            <a href="/games" class="btn btn-primary">List Games</a>
        </div>
    </div>

@endsection

Create a symbolic link from public/storage to storage/app/public

Using the method we have taken above, images are going to be stored in the storage directory, or more specifically in storage/app/public/images. This location is not accessible using web requests. In order to enable this, we need to create a symlink from public/storage to storage/app/public. There is an easy command you can run to do this for you: php artisan storage:link. You can run this right from the root directory of your project. If you have trouble with this command when running homestead on virtualbox or vmware workstation on Windows, you will need to manually create the symlink on the host (Windows) operating system and this will also work. This is because Symlinks often fail in Linux guest operating systems using Vagrant with VMWare Workstation or Virtualbox on Windows. In our example, we followed the format as follows from the windows command prompt with administrator privileges and it worked very well.

mklink /D C:\localdev\54\public\storage C:\localdev\54\storage\app\public

Let’s add one more game to be sure everything works.
form submit with file upload laravel

It looks like it is working great!
file upload and display success


How To Set Up Form Submission In Laravel Summary

We took a pretty big step in this tutorial. Initially, we were manually adding information to the database to work with. In this tutorial, we were able to set up a brand new form to accept information about a game, and add it to the database. Additionally, we covered the topic of using a form to upload an image file to the application, and then how to set up storage and a symlink to be able to display that image in a view. Excellent!

Click to share! ⬇️