Skip to main content

How Easy is Laravel's Workflow?

I've decided to write about application workflow with Laravel 5.2.  One of the handiest features of Laravel is the command line "make" command.  This allows Laravel to automatically create assets for you, place them in the appropriate folder and add classes to the Composer routing table.  So, for example, if you want to create a "widgets" model for your project, we would need to create the migration file (which creates the table) and a model file.  So we will issue two commands in our console window:
  • php artisan make:migration Widgets
  • php artisan make:model Widget
This will create the following files:

  • /database/migrations/<date + timestamp>Widgets.php
  • /app/Widget.php

Note the respective use plural/singular.  In general, we'd be creating a "widgets" table, so the migration name would be similar.  I've found the use of camel case to be the clearest here, though the documentation specifies snake case, which for me, makes it hard to find migrations.  I also leave off the "Create" verb, since this is the first migration for this table.  I would, however, add the "Alter" verb for subsequent migrations on that table.

Models typically describe a single entity, so we use singular for that.  Although we may use the model to retrieve multiple instances of that entity, it still is meant to describe a single entity.  We'll come back to what needs to be done in the migration/model in a minute.

Additionally, for tables where there is a known, finite number of rows (aka, reference tables, such as for user types and so on), we may want to make a seeder file.
  • php artisan make:seeder WidgetSeeder
This creates the following file:
  • /database/seeders/WidgetSeeder.php

Finally, if we need a controller to handle views or form posts for our widgets, we would want to create a controller:
  • php artisan make:controller WidgetController
This creates the following file:
  • /app/http/controllers/WidgetController.php

We haven't defined what a widget is yet, so our migration file generated by Laravel is just going to be a skeleton:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Widgets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

The up() and down() functions in the migration create and drop our tables. When I first started using Laravel, I wasn't sure why we needed a down() function.  It seemed like an annoying extra step, but we'll find out why we need it in a minute.  For now, let's fill in the functions:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class Widgets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('widgets', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
$table->integer('type_id')->unsigned()->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('widgets');
}
}
Most of this should be fairly self-explanatory, if you know about database tables. The increments() method creates an auto-increment column. The timestamps() method creates the created_at and updated_at fields, which Laravel magically fills in for you whenever you insert or update a row.  The softDeletes() method creates a deleted_at column, which Laravel fills in any time you call the delete() method on a model. Soft-deleted rows do not show up when you query a model unless you add the withTrashed() method.  You can also un-delete a row by using the model's restore() method.

Notice the type_id column. This is going to relate to a widget_types table.  Our widget_types table is going to have an auto-increment `id` column, so we want to match the auto-increment column type in the widgets table.  That means it has to be an unsigned integer.  Why did we make it nullable?  That is because we are going to add a foreign key relationship:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class Widgets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('widgets', function (Blueprint $table) {
// Add columns
$table->increments('id');
$table->string('name');
$table->string('description');
$table->integer('type_id')->unsigned()->nullable();
$table->timestamps();
$table->softDeletes();
// Add foreign keys
$table->foreign('type_id') ->references('id') ->on('widget_types') ->onUpdate('cascade') ->onDelete('set null');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Drop foreign keys
Schema::table('widgets', function (Blueprint $table) {
$table->dropForeign('widgets_type_id_foreign');
});
// Drop table
Schema::drop('widgets');
}
}
This answers the two questions we raised above.  First, we need our reference column to be nullable in case a key gets deleted from our foreign table.  This allows us to keep our widget but sets the type_id column to NULL if the foreign key gets deleted.  Second, we learn that the down() method allows us to drop our foreign key before we drop the table, otherwise, we would get a database integrity error when we try to drop our table.  This can be especially annoying when we are building out the initial application and we are migrating and resetting our tables often.

So now that we've built our widgets table, we just need to migrate the table:
  • php artisan migrate
That will create our table and set up any foreign key relationships.  After that, we may want to seed the table with some sample or reference data.  If we have a known set of widgets, we can use that data.  If not, we can use the faker library to create some.  Let's add a finite set of data to our seeder first.  Most of the seeder file is already filled in for us.  We just need to fill out the run() method:
<?php
use Illuminate\Database\Seeder;
class WidgetSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$items = [
['name' => 'Widget 1', 'Description' => 'The first widget.'],
['name' => 'Widget 2', 'Description' => 'The second widget.'],
['name' => 'Widget 3', 'Description' => 'The third widget.'],
['name' => 'Widget 4', 'Description' => 'The fourth widget.'],
['name' => 'Widget 5', 'Description' => 'The fifth widget.'],
];
foreach ($items as $item) {
App\Widget::create($item);
}
}
}
Each line of the $items array represents a widgets row.  We're able to leave off the type_id column here because we made it nullable.  The created_at and updated_at will be automatically time-stamped the moment you run the seeder.

Notice that we used the fully-qualified namespace for our Widget model. We could have just as easily included a "use" statement but I like to make seeders as easily duplicated as possible and that just adds another wrinkle.  That's also why I chose $items, which I will copy to other seeders.  Since many tables will only have a name and description column, the only things we will have to change from migration to migration will be the model name and the data itself.

Additionally, we'll need to update the /database/seeders/DatabaseSeeder.php file, which runs all of our seeders.  Mine is set up like so:
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
// Seeders to run (order matters!)
private $seeders = [
'WidgetSeeder' => 'widgets',
];
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// Clear the error log on each run
file_put_contents(__DIR__ . '/../../storage/logs/laravel.log', '');
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
foreach ($this->seeders as $seeder => $table) {
// Truncate the table, if any
if ($table) {
DB::table($table)->truncate();
}
// Run the seeder
$this->call($seeder);
}
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
}
}

The clearing of the application log is optional.  I like to keep my log clean...and also I like to have important seed data (like randomly-generated passwords) at the top of my log, so I clear it on each run.  We definitely want to disable foreign key constraints so we can truncate our tables without worrying about the order.  As far as calling the seeders, Laravel is going to look for the classes in the same directory.  Some people put the seeder classes in the same file, which is fine but can get messy if you have more than a few seeders.

Important note: our migration will NOT WORK at this point.  To understand why, we need to look at our Laravel-generated model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Widget extends Model
{
//
}

While the model looks pretty sparse, there's actually a lot of magic taking place that makes this model fairly functional.  Laravel assumes that the table related to this model is the lowercase/plural of the model name, so we can query the table through the model with no mention of the table name.  If for some reason your table name didn't match Laravel's expectations, you'd have to declare it:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Widget extends Model
{
// Table
protected $table = 'puppies';
}

That aside, the reason our seeder won't work is because it uses "mass assignment".  In other words, instead of instantiating a new widget, setting all of its properties and then saving the new widget, we are simply using the create() method.  That means that the model needs to know which fields are acceptable for mass assignment.  I don't mind this step, as it is good practice for data integrity, making sure fields can't be updated the wrong way.  So we add the $fillable property like so:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Widget extends Model
{
// Fillable fields
protected $fillable = [
'name',
'description',
'type_id',
];
}

Now our seeder knows it can mass-assign those properties and our seeder's create() method will work.  To run the seeder, we would type the following into our console:

  • php artisan db:seed
If things don't go as planned, you can run db:seed as many times as you like.  Just keep in mind that all seeder tables will be truncated, so you probably don't want to run the seeder in production after the initial seeding.

Lastly, we probably want to do something with the widget data.  For that, we'll need to look at our WidgetController.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
class WidgetController extends Controller
{
//
}
First, we'll add a use statement for our widget class.  Then we'll add a method to list our widgets:
<?php
namespace App\Http\Controllers;
use App\Http\Requests;
use App\Widget;
use Illuminate\Http\Request;
class WidgetController extends Controller
{
/**
* List widgets
*/
public function listWidgets()
{
// Return an array of widgets
return Widget::get()->toArray();
}
}
If we wanted this to be a RESTful controller, we'd just make a few minor changes:
<?php
namespace App\Http\Controllers;
use App\Http\Requests;
use App\Widget;
use Illuminate\Http\Request;
class WidgetController extends Controller
{
/**
* List widgets
*/
public function index()
{
// Get widgets
$widgets = Widget::get();
// Return widgets as json
return response()->json($widgets);
}
}
Notice we changed the method name to index().  That is so that in our routes.php file, we can refer to our RESTful controller as a "resource".  That brings us to the last piece in our puzzle - the routing.  Let's wire up a RESTful route:
<?php
// API v1 Routes
Route::group(['prefix' => 'v1'], function () {
// Widgets
Route::resource('widget', 'WidgetController');
}
There's some debate about whether to put API version numbers in the URL or not but that is beyond the scope of this article.  Let's just say that it is for the sake of simplicity and to separate our API from our non-API routes.  So the first line tells us that every route in the group will have the URL prefix /v1/.  Remember that when you are working in other files (like when setting up your exception handler - you can query the route prefix to decide whether to return a blade response or a JSON response).

The other magic happening here is that we don't need route verbs when setting up a RESTful resource.  The verbs are automatically tied to controller methods (see here).  So our controller's index() method is tied to the route's GET method and so on.  So if we type the URL /v1/widget into POSTman, it would return our widgets:
[
   {
      "name":"Widget 1",
      "Description":"The first widget."
   },
   {
      "name":"Widget 2",
      "Description":"The second widget."
   },
   {
      "name":"Widget 3",
      "Description":"The third widget."
   },
   {
      "name":"Widget 4",
      "Description":"The fourth widget."
   },
   {
      "name":"Widget 5",
      "Description":"The fifth widget."
   }
]
The last thing I'll mention is that you'd probably never have a controller that returns an array of widgets.  More likely, you will return an entire response.  For this, Laravel has a handy Blade system.  I won't go into detail on blade templates in this article but suffice to say that they are basically interchangeable and embedable HTML modules.  So the non-API controller method above would probably look more like this:
<?php
namespace App\Http\Controllers;
use App\Http\Requests;
use App\Widget;
use Illuminate\Http\Request;
class WidgetController extends Controller
{
/**
* List widgets
*/
public function index()
{
// Set the page title
$params['title'] = 'List of Widgets';
// Get widgets
$params['widgets'] = Widget::get();
// Return widgets list
return view('home.widgets', $params);
}
}
Assuming your blade templates are set up properly, this will display the "home" blade template with the "widgets" template embedded in it.  Within these templates, you will be able to refer to the parameters we passed like so:
<h1>{{ $title }}</h2>
...and...
@foreach ($widgets as $widget)
{{ $widget->name }}
@endforeach
I may do an article about blade templates later.  I do hope that this information has been helpful to anyone that comes across it.  I know these are some of the things I wish I'd had more clearly explained to me when I started using Laravel.

Comments

Popular posts from this blog

jQuery noUIslider Pip Label Customization

Recently, I was tasked with creating a slider element for a user to select their credit rating, of which the user can select from among: 'Poor', 'Fair', 'Good', 'Excellent' and 'Not Sure'.  Several problems presented themselves here: A drop-down box would be better, but that's not what the requirements specified. I already have several numeric sliders on my page and I want them all to match. I selected the jQuery noUi Slider library to create the numeric sliders. noUi Slider does not support string values - only numeric values. The "pips" (slider scale/labels) also does not support string values. Since the solution involved shifting my mindset, I wanted to document it here for future reference and maybe to help someone facing the same requirements. Here's my solution: Since there is really no numeric correlation to credit score (especially considering one of the options is "Not Sure"), I will just u

How to Create a new Case Record Type in Salesforce

Here are the specific steps for adding a case record type. Some steps may vary by organization but the process should be pretty similar to what is shown here. 1) Create a new page layout 2) Add any new case statuses to picklist 3) Edit case statuses that should indicate case is "closed" 4) Add any new case types to picklist 5) Add any new case reasons to picklist 6) Add any new case origins to picklist 7) Add any new fields to case object 8) Under Record Types, ensure the picklists have the correct values (i.e. - Reason/Type/Origin) 9) Within the Type field, edit "Reason" field dependencies matrix and add new reason(s) to types 10) Create a new support process (if not reusing an existing one) 11) Create the new record type (enable for all profiles) 12) Finalize the page layout (if needed) and check "Default" in Case Assignment checkbox under Layout Properties 13) Create approval queues (if needed) 14) Set up approv

View Helpers - Custom Output Parser

I was reviewing some blade view code recently and decided it was too verbose. There were some long output statements that were duplicated throughout the view and that always signals a need to refactor or come up with a better method. So I wanted to share (or at least document) what I came up with here. First, the issue: fields stored as boolean "0/1" in the database must be output in "Yes/No" format. Plus, if the field is blank (or null), that must be reflected as well. Add to that the uncertainty of whether the production Microsoft Azure database will return a string or integer value for 1 or 0. This problem necessitates a blade statement like: {{ $object->property === null || $object->property === "" ? "" : $object->property == 1 ? 'Yes' : 'No' }} Like I said...verbose! One solution might be to create a Laravel accessor for the boolean field in the model. But that would mean that if I want the actual boolean