12

Creating a Basic ToDo Application With Laravel 4 – Part 1

Posted December 2nd, 2013 (Updated 8 Apr 2014) in PHP

Laravel may be one of the younger frameworks out there but it’s making ripples in the PHP world. The following post teaches how to build a basic to-do application in Laravel 4. It covers a wide range of concepts, links to relevant learning material where possible and should make for a great introduction to the framework.

This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation and Slugs

Today will cover installation, configuration, artisan, migration, seeding and routes.

 

Before you Begin

Before you begin there are a few great resources you should check out.

You won’t get far in the Laravel world without hearing about Jeffrey Way. Jeffrey has perhaps done more for the Laravel community than any other non-core developer. He has produced high-quality, comprehensive video tutorials on almost every aspect of L4, many of which are free to view and aimed at beginners. I would highly recommend you check out the following pieces of his work:

  1. Laravel 4 Mastery - although older (from the L4 beta days), it’s mostly still relevant. Aimed at beginners and quite comprehensive
  2. Laravel From Scratch - in many ways an updated version of Laravel 4 Mastery. Not as comprehensive but a great compliment to the above
  3. Laracasts - Mostly paid for videos of very high quality. New videos are regularly created and one a week is made free.

There are a few other places to find news and information:

  1. The Laravel Twitter feed - for the latest breaking news on L4 development
  2. Laravel.io - Weekly roundups  that gather the latest news and tutorials from around the web. They also do a weekly podcast that often includes the framework author Taylor Otwell
  3. Laravel Packages Registry - good place to go to find some of the best L4 packages
  4. Code Bright – An e-book provided free of charge by framework author Dayle Rees

 

Project Aim

Our to-do application will consist of one or more projects, each with its own list of tasks. You will be able to create, list, modify and delete both tasks and projects.

In this lesson we will go through:

  1. Installing and setting up Laravel 4
  2. Installing extra packages that will make development easier
  3. Using migrations and seeds
  4. Learning how to use resourceful controllers
  5. Learning how to use views (including the blade templating language and content layouts
  6. Handling model relations

 

Installation & Setup

Laravel and Composer

Installing L4 is extremely quick and painless thanks to Composer. I’ll blaze through this section as it’s really been covered to death by this point.

First you’ll need to install Composer if you haven’t already, then laravel 4

1
2
3
4
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
composer create-project laravel/laravel l4todo --prefer-dist
cd l4todo

You should now have a shiny new l4todo folder with Laravel inside. Congratulations, installation complete!

Optional Handy Tip: Make Laravel Easier to Install

Laravel now has its own phar archive making installation easier than ever. You’ll still need composer, however you won’t need to remember the difficult to remember composer create-project line for new installations. To install the phar archive do the following:

wget http://laravel.com/laravel.phar
mv laravel.phar /usr/local/bin/laravel
chmod +x /usr/local/bin

You can now use laravel new projectname to create a new project!

 

Database

We need a database. Set one up for yourself in a DB of your choice then open /app/config/database.php and set your credentials in the connections section. It should be fairly straight forward, here’s mine:

1
2
3
4
5
6
7
8
9
10
'mysql' => array(
	'driver'    => 'mysql',
	'host'      => 'localhost',
	'database'  => 'l4todo',
	'username'  => 'my_username',
	'password'  => 'my_password',
	'charset'   => 'utf8',
	'collation' => 'utf8_unicode_ci',
	'prefix'    => '',
),

Optional Handy Tip: Set config as environment variables

It’s often a good idea to store credentials such as those above in environment variables rather than hardcoding them to your configuration file. This allows you to publish your Laravel application to GitHub without fear of your credentials being visible to the world. This is done through environment configuration.

Begin by opening /bootstrap/start.php and replacing.

1
2
3
$env = $app->detectEnvironment(array(
	'local' => array('your-machine-name'),
));

with

1
2
3
$env = $app->detectEnvironment(function() {
	return 'local';
});

The above tells Laravel, amongst other things, to load the .env.local.php file in your root folder. This is where we’ll define our environment variables:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
 
return [
	// Database vars
	'DB_HOST' => 'localhost',
	'DB_USER' => 'my_username',
	'DB_PASS' => 'my_password',
	'DB_NAME' => 'my_database',
 
	// Any other vars you want
	// ...
];

And now to use these values in our /app/config/database.php:

1
2
3
4
5
6
7
8
9
10
'mysql' => array(
	'driver'    => 'mysql',
	'host'      => getenv('DB_HOST'),
	'database'  => getenv('DB_NAME'),
	'username'  => getenv('DB_USER'),
	'password'  => getenv('DB_PASS'),
	'charset'   => 'utf8',
	'collation' => 'utf8_unicode_ci',
	'prefix'    => '',
),

Remember to add your environment files to your .gitignore by adding a .env.* line! For more information see Protecting Sensitive Configuration in the docs.

 

Extra Packages

Laravel 4 already has a large number of packages written by developers that provide extra tools and functionality. The Laravel Packages Repository hosts a great many of these. We’ll install one that will be particularly useful for this project.

Laravel 4 Generators

L4 Generators extend artisan allowing it to handle a bunch of boilerplate code generation for us. Follow the installation instructions on the GitHub page then watch this video to see exactly what the package can do for you.

 

Taking our First Steps

As mentioned above, our to-do application will comprise of one or more projects each with their own task list. We’re going to need Project and Task models, controllers, views, migrations, seeds (optional but useful) and routes. You probably already understand what most/all of these are, however if you don’t you should check out the video M-V-Huh?.

To cut a long story short, we’ll create the foundation for all of the above with two simple commands thanks to the generators we installed earlier. Enter yes to every question except Do you want to go ahead and migrate the database? [yes|no]:

1
2
php artisan generate:resource project --fields="name:string, slug:string"
php artisan generate:resource task --fields="project_id:integer, name:string, slug:string, completed:boolean, description:text"

Whew! That did alot of stuff. We now have all of the above, time to play with it and make it do our evil bidding.

 

Migrations & Schema Relations

Further details: See Running Migrations

We want to get our table schema into the database. It will look like the following:

Projects
+------------+------------------+------+-----+
| Field      | Type             | Null | Key |
+------------+------------------+------+-----+
| id         | int(10) unsigned | NO   | PRI |
| name       | varchar(255)     | NO   |     |
| slug       | varchar(255)     | NO   |     |
| created_at | timestamp        | NO   |     |
| updated_at | timestamp        | NO   |     |
+------------+------------------+------+-----+
 
Tasks
+-------------+------------------+------+-----+
| Field       | Type             | Null | Key |
+-------------+------------------+------+-----+
| id          | int(10) unsigned | NO   | PRI |
| project_id  | int(10) unsigned | NO   | MUL |
| name        | varchar(255)     | NO   |     |
| slug        | varchar(255)     | NO   |     |
| completed   | tinyint(1)       | NO   |     |
| description | text             | NO   |     |
| created_at  | timestamp        | NO   |     |
| updated_at  | timestamp        | NO   |     |
+-------------+------------------+------+-----+

Open up /app/database/migrations folder and check out your two migrations files. We’re going to want to use an index and foreign key constraint on our tasks table’s project_id field for speed and reliability. This poses a problem in that if we migrate, the projects and tasks tables are created sequentially and everything will work fine, however if we php artisan migrate:rollback, they’ll drop in the same order and an SQL error will occur due to tasks relying on the projects table. We could simply switch the lines in the down() methods, but we’ll do it properly instead.

  1. Move the contents of the tasks migration’s up() an down() methods into the projects migration and reverse the drop() order. Delete the tasks migration. You should now have:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    <?php
     
    use IlluminateDatabaseMigrationsMigration;
    use IlluminateDatabaseSchemaBlueprint;
     
    class CreateProjectsTable extends Migration {
     
    	/**
    	 * Run the migrations.
    	 *
    	 * @return void
    	 */
    	public function up()
    	{
    		Schema::create('projects', function(Blueprint $table) {
    			$table->increments('id');
    			$table->string('name');
    			$table->string('slug');
    			$table->timestamps();
    		});
     		Schema::create('tasks', function(Blueprint $table) {			$table->increments('id');			$table->string('project_id');			$table->string('name');			$table->string('slug');			$table->boolean('completed');			$table->text('description');			$table->timestamps();		});	}
     
    	/**
    	 * Reverse the migrations.
    	 *
    	 * @return void
    	 */
    	public function down()
    	{
    		Schema::drop('tasks');		Schema::drop('projects');
    	}
     
    }
  2. Add the index and foreign key constraint to the project_id field and make it unsigned to match the project table’s id field:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    
    <?php
     
    use IlluminateDatabaseMigrationsMigration;
    use IlluminateDatabaseSchemaBlueprint;
     
    class CreateProjectsTable extends Migration {
     
    	/**
    	 * Run the migrations.
    	 *
    	 * @return void
    	 */
    	public function up()
    	{
    		Schema::create('projects', function(Blueprint $table) {
    			$table->increments('id');
    			$table->string('name');
    			$table->string('slug');
    			$table->timestamps();
    		});
     
    		Schema::create('tasks', function(Blueprint $table) {
    			$table->increments('id');
    			$table->integer('project_id')->unsigned();			$table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');			$table->string('name');
    			$table->string('slug');
    			$table->boolean('completed');
    			$table->text('description');
    			$table->timestamps();
    		});
    	}
     
    	/**
    	 * Reverse the migrations.
    	 *
    	 * @return void
    	 */
    	public function down()
    	{
    		Schema::drop('tasks');
    		Schema::drop('projects');
    	}
     
    }

One thing to note with the above is the onDelete(‘cascade’) call. This will mean on deletion of a project, all related tasks will also be deleted. Without it an error will occur as our foreign key constraint protects against orphaned entries.

You should now be able to call migrate as many times as you want and it’ll work:

1
php artisan migrate:refresh

Check your database. The tables will have been successfully added:

CREATE TABLE `projects` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) COLLATE utf8_unicode_ci NOT NULL,
  `slug` VARCHAR(255) COLLATE utf8_unicode_ci NOT NULL,
  `created_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
 
CREATE TABLE `tasks` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `project_id` INT(10) UNSIGNED NOT NULL,
  `name` VARCHAR(255) COLLATE utf8_unicode_ci NOT NULL,
  `slug` VARCHAR(255) COLLATE utf8_unicode_ci NOT NULL,
  `completed` tinyint(1) NOT NULL,
  `description` text COLLATE utf8_unicode_ci NOT NULL,
  `created_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  KEY `tasks_project_id_foreign` (`project_id`),
  CONSTRAINT `tasks_project_id_foreign` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

 

Seeds

Further details: See Database Seeding

We’ll seed some projects/tasks to have something to work with when we finally get to the browser. Open /app/database/seeds/ProjectsTableSeeder.php and TasksTableSeeder.php. You can drop whatever you want in here. I added 3 projects and 5 tasks. Don’t forget the created_at and updated_at columns which are automatically added!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class ProjectsTableSeeder extends Seeder {
 
	public function run()
	{
		// Uncomment the below to wipe the table clean before populating
		// DB::table('projects')->truncate();
 
		$projects = array(			['name' => 'Project 1', 'slug' => 'project-1', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Project 2', 'slug' => 'project-2', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Project 3', 'slug' => 'project-3', 'created_at' => new DateTime, 'updated_at' => new DateTime],		); 
		// Uncomment the below to run the seeder
		DB::table('projects')->insert($projects);	}
 
}
 
class TasksTableSeeder extends Seeder {
 
	public function run()
	{
		// Uncomment the below to wipe the table clean before populating
		// DB::table('tasks')->truncate();
 
		$tasks = array(			['name' => 'Task 1', 'slug' => 'task-1', 'project_id' => 1, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Task 2', 'slug' => 'task-2', 'project_id' => 1, 'description' => 'My second task', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Task 3', 'slug' => 'task-3', 'project_id' => 1, 'description' => 'My third task', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Task 4', 'slug' => 'task-4', 'project_id' => 2, 'description' => 'My fourth task', 'created_at' => new DateTime, 'updated_at' => new DateTime],			['name' => 'Task 5', 'slug' => 'task-5', 'project_id' => 2, 'description' => 'My fifth task', 'created_at' => new DateTime, 'updated_at' => new DateTime],		); 
		// Uncomment the below to run the seeder
		DB::table('tasks')->insert($tasks);	}
 
}

Also don’t forget to add your seed classes to /app/database/seeds/DatabaseSeeder.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DatabaseSeeder extends Seeder {
 
	/**
	 * Run the database seeds.
	 *
	 * @return void
	 */
	public function run()
	{
		Eloquent::unguard();
 
		// $this->call('UserTableSeeder');
		$this->call('ProjectsTableSeeder');		$this->call('TasksTableSeeder');	}
 
}

Now we seed:

1
2
3
php artisan db:seed
# or
php artisan migrate:refresh --seed

Your database should now be seeded!

select * from projects;
+----+-----------+-----------+---------------------+---------------------+
| id | name      | slug      | created_at          | updated_at          |
+----+-----------+-----------+---------------------+---------------------+
|  1 | Project 1 | project-1 | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  2 | Project 2 | project-2 | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  3 | Project 3 | project-3 | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
+----+-----------+-----------+---------------------+---------------------+
3 rows in set (0.00 sec)
 
select * from tasks;
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+
| id | project_id | name   | slug   | completed | description    | created_at          | updated_at          |
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+
|  1 |          1 | Task 1 | task-1 |         0 | My first task  | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  2 |          1 | Task 2 | task-2 |         0 | My second task | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  3 |          1 | Task 3 | task-3 |         0 | My third task  | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  4 |          2 | Task 4 | task-4 |         0 | My fourth task | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
|  5 |          2 | Task 5 | task-5 |         0 | My fifth task  | 2013-11-28 19:45:46 | 2013-11-28 19:45:46 |
+----+------------+--------+--------+-----------+----------------+---------------------+---------------------+

 

Artisan – Tinker

Now that we have information in the database it would be a great time to learn about one of artisan’s handy features – tinker. As explained in the informative article Tinkering with Tinker Like an Artisan, tinker provides a command line for interacting with your Laravel installation. As an example, let’s use it to retrieve the number of projects currently in the database:

$ php artisan tinker
>echo Project::count();    
3

As you can see tinker has the potential to be quite useful. I’ll be referencing it a few times in this tutorial.

 

Routes – Nested Resources

Further details: See Nested Resources

Begin by adding the Project and Task resources to /app/routes.php:

1
2
3
4
5
6
Route::get('/', function()
{
	return View::make('hello');
});
Route::resource('projects', 'ProjectsController');Route::resource('tasks', 'TasksController');

Let’s now look at a neat little artisan feature – routes. In your command line enter the following:

$ php artisan routes
+--------+-------------------------------+------------------+----------------------------+----------------+---------------+
| Domain | URI                           | Name             | Action                     | Before Filters | After Filters |
+--------+-------------------------------+------------------+----------------------------+----------------+---------------+
|        | GET /                         |                  | Closure                    |                |               |
|        | GET /projects                 | projects.index   | ProjectsController@index   |                |               |
|        | GET /projects/create          | projects.create  | ProjectsController@create  |                |               |
|        | POST /projects                | projects.store   | ProjectsController@store   |                |               |
|        | GET /projects/{projects}      | projects.show    | ProjectsController@show    |                |               |
|        | GET /projects/{projects}/edit | projects.edit    | ProjectsController@edit    |                |               |
|        | PUT /projects/{projects}      | projects.update  | ProjectsController@update  |                |               |
|        | PATCH /projects/{projects}    |                  | ProjectsController@update  |                |               |
|        | DELETE /projects/{projects}   | projects.destroy | ProjectsController@destroy |                |               |
|        | GET /tasks                    | tasks.index      | TasksController@index      |                |               |
|        | GET /tasks/create             | tasks.create     | TasksController@create     |                |               |
|        | POST /tasks                   | tasks.store      | TasksController@store      |                |               |
|        | GET /tasks/{tasks}            | tasks.show       | TasksController@show       |                |               |
|        | GET /tasks/{tasks}/edit       | tasks.edit       | TasksController@edit       |                |               |
|        | PUT /tasks/{tasks}            | tasks.update     | TasksController@update     |                |               |
|        | PATCH /tasks/{tasks}          |                  | TasksController@update     |                |               |
|        | DELETE /tasks/{tasks}         | tasks.destroy    | TasksController@destroy    |                |               |
+--------+-------------------------------+------------------+----------------------------+----------------+---------------+

You’ll notice that both projects and tasks are top level urls. In our to-do app, tasks belong to projects though, so it makes sense for URLs to be nested more like /projects/1/tasks/3 instead of just /tasks/3. This can be accomplished using something called nested resources. As with most things in L4, the modification required is quick and simple. Open /app/routes.php and make the following change:

1
2
// Route::resource('tasks', 'TasksController');
Route::resource('projects.tasks', 'TasksController');

That’s it. Do another php artisan routes and see what you have now:

$ php artisan routes
+--------+---------------------------------------------+------------------------+----------------------------+----------------+---------------+
| Domain | URI                                         | Name                   | Action                     | Before Filters | After Filters |
+--------+---------------------------------------------+------------------------+----------------------------+----------------+---------------+
|        | GET /                                       |                        | Closure                    |                |               |
|        | GET /projects                               | projects.index         | ProjectsController@index   |                |               |
|        | GET /projects/create                        | projects.create        | ProjectsController@create  |                |               |
|        | POST /projects                              | projects.store         | ProjectsController@store   |                |               |
|        | GET /projects/{projects}                    | projects.show          | ProjectsController@show    |                |               |
|        | GET /projects/{projects}/edit               | projects.edit          | ProjectsController@edit    |                |               |
|        | PUT /projects/{projects}                    | projects.update        | ProjectsController@update  |                |               |
|        | PATCH /projects/{projects}                  |                        | ProjectsController@update  |                |               |
|        | DELETE /projects/{projects}                 | projects.destroy       | ProjectsController@destroy |                |               |
|        | GET /projects/{projects}/tasks              | projects.tasks.index   | TasksController@index      |                |               |
|        | GET /projects/{projects}/tasks/create       | projects.tasks.create  | TasksController@create     |                |               |
|        | POST /projects/{projects}/tasks             | projects.tasks.store   | TasksController@store      |                |               |
|        | GET /projects/{projects}/tasks/{tasks}      | projects.tasks.show    | TasksController@show       |                |               |
|        | GET /projects/{projects}/tasks/{tasks}/edit | projects.tasks.edit    | TasksController@edit       |                |               |
|        | PUT /projects/{projects}/tasks/{tasks}      | projects.tasks.update  | TasksController@update     |                |               |
|        | PATCH /projects/{projects}/tasks/{tasks}    |                        | TasksController@update     |                |               |
|        | DELETE /projects/{projects}/tasks/{tasks}   | projects.tasks.destroy | TasksController@destroy    |                |               |
+--------+---------------------------------------------+------------------------+----------------------------+----------------+---------------+

 

Setting Slug-based URLs

We’ve almost got our routes perfect however in their current state we’ll have URLs like /projects/1/tasks/2. It would be much better for our visitors if the model IDs were replaced with their respective slug fields instead. So we’d get for example /projects/my-first-project/tasks/buy-milk.

Open /app/routes.php and drop the following in:

1
2
3
4
5
6
Route::bind('tasks', function($value, $route) {
	return Task::whereSlug($value)->first();
});
Route::bind('projects', function($value, $route) {
	return Project::whereSlug($value)->first();
});

the above will override the default behavior for the tasks and projects wildcards in php artisan routes.

 

Conclusion

Today we covered:

  • installed and configured Laravel
  • created two resources
  • set up our migrations
  • added some data seeds
  • configured our URL structure

We laid the groundwork for the next lesson by creating all the components required for the frontend to function. We now have data in our database and routes to hit. In the next lesson we’ll get started on the frontend of the site!

 

  • ammine007

    Salam,
    Very Helpful resource ! Thank you very much !

  • Khoparzi

    The default php install on OSX does not have pnctl installed. This can be problematic for using Tinker (or boris).

    If you are getting ‘function not found’ errors with Tinker, the steps in this link should help

    http://www.php.net/manual/en/pcntl.installation.php

  • Periyasamy
    • flynsarmy

      Trash

  • Max R

    The migration fails because the ‘project_id’ column is created as a int(11). ‘id’ in the projects table is created as int(10)

    This is the error message I get:
    [IlluminateDatabaseQueryException]

    SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `tasks` add constraint tasks_project_id_foreign foreign key (`project_id`) references `projects` (`id`) on delete cascade)

    How can I solve this? Thanks for the tutorial!

    • flynsarmy

      Try changing ->integer('project_id')->unsigned() to ->unsignedInteger('project_id') as per this post.

  • Max R

    Figured it out…

    In composer.json use the stable option:
    “minimum-stability”: “stable”

  • http://www.webradish.com Webradish

    Exciting stuff! But I got up to php artisan migrate:refresh, then got an error: “the use statement with non-compound name ‘IlluminateDatbaseMigrationsMigration’ has no effect.
    It was going so well too? Any chance of a hint on where I went wrong?

    • flynsarmy

      One of your migrations is using the ‘use’ statement incorrectly. Try reading this.

      • http://www.webradish.com Webradish

        Yes I think I just had to add some backslashes ie: use IlluminateDatabaseMigrationsMigration;
        use IlluminateDatabaseSchemaBlueprint;

  • Mats

    Hi,

    Nowhere is there mentioned, with regards to seeding, that you have to add ProjectTableSeeder and TaskTableSeeder to DatabaseSeeder.php:

    $this->call(‘ProjectTAbleSeeder’);
    $this->call(‘TaskTAbleSeeder’);

    • flynsarmy

      Fixed. Thanks for the reminder :)