ThinkPHP, 国内开发的框架,特别容易入门,中文文档细致,表述准确。
Laravel, 国外框架,非常高级的一个框架,特别是前端比较模块化,但入门难一些,速度不高。
laravel在lampp下的安装方法:
1、先安装composer
安装地址:https://getcomposer.org/download/ 。
可以使用 /opt/lampp/bin/php composer-setup.php --install-dir=bin 将composer安装到你想要的位置。
安装好的 composer.phar 文件其实就是一个php文件,用php解析它就可以了。
2、通过 composer 安装 laravel
/opt/lampp/bin/php /opt/lampp/bin/composer.phar create-project --prefer-dist laravel/laravel blog
(上面这句等同于laravel的tutorial的 composer create-project --prefer-dist laravel/laravel blog
这句)
这句会安装根据laravel安装一个blog网站,放在当前目录下。若安装时找不到php命令,可以在命令行将 /opt/lampp/bin 临时加入环境变量 PATH。
3、也可以将 laravel 安装成全局的:
composer global require "laravel/installer"
4、laravel的入口是在网站目录的public目录,记得用apache的权限限制网站的大部分文件不能外部访问,laravel默认没有加入 apache 的这项配置。
laravel的例子: https://www.flynsarmy.com/2015/02/creating-a-basic-todo-application-in-laravel-5-part-1/
源码: https://github.com/Flynsarmy/laravel-5-todo-tutorial
Creating a Basic ToDo Application in Laravel 5 – Part 1
With the release of Laravel 5, there have been a bunch of backwards incompatible changes, new features added as well as the usual influx of new users so I thought I’d take the time to redo my basic to-do application with Laravel 5. The app 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.
- Part 1 – Installation, Database and Routes
- Part 2 – Listing Projects and Tasks
- Part 3 – Create/Edit/Delete
- Part 4 – Validation
The source for each part can also be found on GitHub.
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 L5, many of which are free to view and aimed at beginners. I would highly recommend you check out the following pieces of his work:
- Laravel 5 Fundamentals – An absolute must-see series of free screencasts for new users of Laravel.
- What’s New in Laravel 5 – A great series by Matt Stauffer showing off some cool new stuff in Laravel.
- 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:
- The Laravel Twitter feed – for the latest breaking news on L4 development.
- Taylor Otwell’s Twitter feed – the author of Laravel himself.
- Laravel.io – Weekly roundups that gather the latest news and tutorials from around the web. They also do a weekly podcast covering their thoughts/concerns relevant to the platform.
- Laravel Packages Registry – good place to go to find some of the best Laravel packages
- Code Bright – An e-book provided free of charge by framework author Dayle Rees
If you’re coming from Laravel 4 I also recommend checking out Directory structure and namespace by Matt Stauffer.
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:
- Installing and setting up Laravel
- Installing extra packages that will make development easier
- Using migrations and seeds
- Learning how to use resourceful controllers
- Learning how to use views (including the blade templating language and content layouts)
- Handling model relations
Installation
Installing Laravel 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.
Composer
First you’ll need to install Composer if you haven’t already (you’ll only need to do this once):
1 2 |
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer |
Laravel
Now for the laravel installer (you’ll only need to do this once):
1 |
composer global require "laravel/installer=~1.1" |
and finally our project:
1 |
laravel new l5todo |
Configuration
Laravel 5 uses a package called DotEnv that stores sensitive information in .env files which are loaded as PHP environment variables at runtime. Sounds complicated but it just means your sensitive credentials go into these files while the rest of your config remains in the standard config files.
Database
We need a database. Set one up for yourself in a DB of your choice then copy.env.example to .env and update accordingly:
1 2 3 4 |
DB_HOST=localhost DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret |
Finally if you’re not using MySQL open /config/database.php and change the default line:
1 |
'default' => 'mysql', |
Remember to add your environment files to your .gitignore by adding a .env line!
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 Taskmodels, 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? and Basic Model/Controller/View Workflow.
Let’s make our way through them one at a time.
Migrations
We want to get our table schema set up in 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 | | +-------------+------------------+------+-----+ |
First a migration must be set up:
1 |
php artisan make:migration create_projects_and_tasks_tables --create="projects" |
We’re creating both tables in the one migration so that they can be removed in reverse order to avoid an integrity constraint violation. Open /database/migrations/<date>_create_projects_and_tasks_tables.php and set it up as follows:
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 46 |
<?php use IlluminateDatabaseSchemaBlueprint; use IlluminateDatabaseMigrationsMigration; class CreateProjectsAndTasksTables extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('projects', function(Blueprint $table) { $table->increments('id'); $table->string('name')->default(''); $table->string('slug')->default(''); $table->timestamps(); }); Schema::create('tasks', function(Blueprint $table) { $table->increments('id'); $table->integer('project_id')->unsigned()->default(0); $table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade'); $table->string('name')->default(''); $table->string('slug')->default(''); $table->boolean('completed')->default(false); $table->text('description')->default(''); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('tasks'); Schema::drop('projects'); } } |
Perform the migration:
1 |
php artisan migrate |
If you check the database, your tables should now be all set up.
Seeds
We’ll seed some projects/tasks to have something to work with when we finally get to the browser. Create /database/seeds/ProjectsTableSeeder.phpand TasksTableSeeder.php like so:
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 46 47 48 49 50 51 |
// /database/migrations/seeds/ProjectsTableSeeder.php <?php use IlluminateDatabaseSeeder; class ProjectsTableSeeder extends Seeder { public function run() { // Uncomment the below to wipe the table clean before populating DB::table('projects')->delete(); $projects = array( ['id' => 1, 'name' => 'Project 1', 'slug' => 'project-1', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 2, 'name' => 'Project 2', 'slug' => 'project-2', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 3, '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); } } // /database/migrations/seeds/TasksTableSeeder.php <?php use IlluminateDatabaseSeeder; class TasksTableSeeder extends Seeder { public function run() { // Uncomment the below to wipe the table clean before populating DB::table('tasks')->delete(); $tasks = array( ['id' => 1, 'name' => 'Task 1', 'slug' => 'task-1', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 2, 'name' => 'Task 2', 'slug' => 'task-2', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 3, 'name' => 'Task 3', 'slug' => 'task-3', 'project_id' => 1, 'completed' => false, 'description' => 'My first task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 4, 'name' => 'Task 4', 'slug' => 'task-4', 'project_id' => 1, 'completed' => true, 'description' => 'My second task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 5, 'name' => 'Task 5', 'slug' => 'task-5', 'project_id' => 1, 'completed' => true, 'description' => 'My third task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 6, 'name' => 'Task 6', 'slug' => 'task-6', 'project_id' => 2, 'completed' => true, 'description' => 'My fourth task', 'created_at' => new DateTime, 'updated_at' => new DateTime], ['id' => 7, 'name' => 'Task 7', 'slug' => 'task-7', 'project_id' => 2, 'completed' => false, '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/database/seeds/DatabaseSeeder.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
use IlluminateDatabaseSeeder; use IlluminateDatabaseEloquentModel; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Model::unguard(); $this->call('ProjectsTableSeeder'); $this->call('TasksTableSeeder'); } } |
Now we seed:
1 |
composer dump-autoload |
then
1 2 3 |
php artisan db:seed # or php artisan migrate:refresh --seed |
Your database should now be seeded!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mysql> select * from projects; +----+-----------+-----------+---------------------+---------------------+ | id | name | slug | created_at | updated_at | +----+-----------+-----------+---------------------+---------------------+ | 1 | Project 1 | project-1 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 2 | Project 2 | project-2 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 3 | Project 3 | project-3 | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | +----+-----------+-----------+---------------------+---------------------+ mysql> select * from tasks; +----+------------+--------+--------+-----------+----------------+---------------------+---------------------+ | id | project_id | name | slug | completed | description | created_at | updated_at | +----+------------+--------+--------+-----------+----------------+---------------------+---------------------+ | 1 | 1 | Task 1 | task-1 | 0 | My first task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 2 | 1 | Task 2 | task-2 | 0 | My first task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 3 | 1 | Task 3 | task-3 | 0 | My first task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 4 | 1 | Task 4 | task-4 | 1 | My second task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 5 | 1 | Task 5 | task-5 | 1 | My third task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 6 | 2 | Task 6 | task-6 | 1 | My fourth task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | | 7 | 2 | Task 7 | task-7 | 0 | My fifth task | 2015-02-05 01:25:43 | 2015-02-05 01:25:43 | +----+------------+--------+--------+-----------+----------------+---------------------+---------------------+ |
Models
To work with our projects and tasks tables we need equivalent Models so create them now:
1 2 |
php artisan make:model Project php artisan make:model Task |
That was easy!
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 >AppProject::count(); 3 >AppTask::count(); 7 |
As you can see tinker has the potential to be quite useful. I’ll be referencing it a few times in this tutorial.
Controllers
We’ve gotten to the point now where we can start hitting the browser. To do that we need to set up some Controllers and Routes to point to them. First up the controllers:
1 2 |
php artisan make:controller ProjectsController php artisan make:controller TasksController |
Nested Resources
Begin by adding the Project and Task resources to /app/Http/routes.php:
1 2 3 4 5 6 7 8 9 10 11 |
Route::get('/', 'WelcomeController@index'); //Route::get('home', 'HomeController@index'); // //Route::controllers([ // 'auth' => 'AuthAuthController', // 'password' => 'AuthPasswordController', //]); Route::resource('projects', 'ProjectsController');Route::resource('tasks', 'TasksController'); |
Let’s now look at a neat little artisan feature – route:list. In your command line enter the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
php artisan route:list +--------+----------+--------------------------+------------------+-------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+--------------------------+------------------+-------------------------------------------------+------------+ | | GET|HEAD | / | | AppHttpControllersWelcomeController@index | | | | GET|HEAD | projects | projects.index | AppHttpControllersProjectsController@index | | | | GET|HEAD | projects/create | projects.create | AppHttpControllersProjectsController@create | | | | POST | projects | projects.store | AppHttpControllersProjectsController@store | | | | GET|HEAD | projects/{projects} | projects.show | AppHttpControllersProjectsController@show | | | | GET|HEAD | projects/{projects}/edit | projects.edit | AppHttpControllersProjectsController@edit | | | | PUT | projects/{projects} | projects.update | AppHttpControllersProjectsController@update | | | | PATCH | projects/{projects} | | AppHttpControllersProjectsController@update | | | | DELETE | projects/{projects} | projects.destroy | AppHttpControllersProjectsController@destroy | | | | GET|HEAD | tasks | tasks.index | AppHttpControllersTasksController@index | | | | GET|HEAD | tasks/create | tasks.create | AppHttpControllersTasksController@create | | | | POST | tasks | tasks.store | AppHttpControllersTasksController@store | | | | GET|HEAD | tasks/{tasks} | tasks.show | AppHttpControllersTasksController@show | | | | GET|HEAD | tasks/{tasks}/edit | tasks.edit | AppHttpControllersTasksController@edit | | | | PUT | tasks/{tasks} | tasks.update | AppHttpControllersTasksController@update | | | | PATCH | tasks/{tasks} | | AppHttpControllersTasksController@update | | | | DELETE | tasks/{tasks} | tasks.destroy | AppHttpControllersTasksController@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 Laravel, the modification required is quick and simple. Open /app/Http/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 route:list and see what you have now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
php artisan route:list +--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+ | | GET|HEAD | / | | AppHttpControllersWelcomeController@index | | | | GET|HEAD | projects | projects.index | AppHttpControllersProjectsController@index | | | | GET|HEAD | projects/create | projects.create | AppHttpControllersProjectsController@create | | | | POST | projects | projects.store | AppHttpControllersProjectsController@store | | | | GET|HEAD | projects/{projects} | projects.show | AppHttpControllersProjectsController@show | | | | GET|HEAD | projects/{projects}/edit | projects.edit | AppHttpControllersProjectsController@edit | | | | PUT | projects/{projects} | projects.update | AppHttpControllersProjectsController@update | | | | PATCH | projects/{projects} | | AppHttpControllersProjectsController@update | | | | DELETE | projects/{projects} | projects.destroy | AppHttpControllersProjectsController@destroy | | | | GET|HEAD | projects/{projects}/tasks | projects.tasks.index | AppHttpControllersTasksController@index | | | | GET|HEAD | projects/{projects}/tasks/create | projects.tasks.create | AppHttpControllersTasksController@create | | | | POST | projects/{projects}/tasks | projects.tasks.store | AppHttpControllersTasksController@store | | | | GET|HEAD | projects/{projects}/tasks/{tasks} | projects.tasks.show | AppHttpControllersTasksController@show | | | | GET|HEAD | projects/{projects}/tasks/{tasks}/edit | projects.tasks.edit | AppHttpControllersTasksController@edit | | | | PUT | projects/{projects}/tasks/{tasks} | projects.tasks.update | AppHttpControllersTasksController@update | | | | PATCH | projects/{projects}/tasks/{tasks} | | AppHttpControllersTasksController@update | | | | DELETE | projects/{projects}/tasks/{tasks} | projects.tasks.destroy | AppHttpControllersTasksController@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/Http/routes.php and drop the following in:
1 2 3 4 5 6 |
Route::bind('tasks', function($value, $route) { return AppTask::whereSlug($value)->first(); }); Route::bind('projects', function($value, $route) { return AppProject::whereSlug($value)->first(); }); |
the above will override the default behavior for the tasks andprojects wildcards in php artisan routes.
Conclusion
Today we:
- 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!
Creating a Basic ToDo Application in Laravel 5 – Part 2
Welcome back to my simple to-do application tutorial for Laravel 5. This tutorial is relatively long so I’ve broken it up into multiple posts.
- Part 1 – Installation, Database and Routes
- Part 2 – Listing Projects and Tasks
- Part 3 – Create/Edit/Delete
- Part 4 – Validation
The source for each part can also be found on GitHub.
So far we have a working database complete with seed data and a bunch of routes for displaying, editing and deleting our projects and tasks. In this second chapter I’ll cover controllers, models (with relationships), views (including the blade templating language and layouts) and route model binding.
Laravel Form Helpers
In Laravel 4 we had a HTML package but it was removed for 5 to cut down on cruft. Add it back in by following the instructions in my How to Install Illuminate/HTML In Laravel 5 tutorial. In addition to HTML and Form facades, this package provides some handy helper functions such as link_to_route()which we’ll be using later.
Controllers and Blade View Layouts
If you browse to /projects you’ll get an empty page. Why is that? Well let’s find out. Run php artisan route:list one more time and look at this line:
1 2 3 4 5 |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+ | | GET|HEAD | projects | projects.index | AppHttpControllersProjectsController@index | |+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+ |
Looks like the /projects URL is loading ProjectsController’s index method. So open up /app/Http/controllers/ProjectsController.php and update the method to point to a view we’ll create:
1 2 3 4 |
public function index() { return view('projects.index');} |
We’re using Blade Templates in this tutorial so create a /resources/views/projects/index.blade.php file and enter some text in there. Hit /projects in your browser again. If everything is working correctly you should see the text you entered above. Do the same for the create controller method.
Showing the contents of a view is great, but if we have more than one page on our site we’ll want a consistent template across all pages. In other words, we need the view’s contents to sit inside a basic HTML template. This is done withcontroller layouts.
There are a few steps to implementing controller layouts:
- Create a layout view. Laravel actually ships with a pretty decent one called app.blade.php so we’ll save some time and just use that. Notice near the bottom the layout contains a @yield(‘content’) line. That’s the function that will load our actual content.
- Reference your layout in your view using @extends(‘app’) and wrap in a @section(‘content’) block like so:
1 2 3 4 5
@extends('app') @section('content') This is my /resources/views/projects/index.blade.php file! @endsection
In /resources/views/projects folder create a show.blade.php, index.blade.phpand create.blade.php view with the above markup replacing the filename as necessary. With these in place, refresh /projects in your browser. You should now see the app.blade.php skeleton around your view contents.
Route Model Binding
By default Laravel will provide an ID value to various resourceful controller methods such as show(), edit(), update() and destroy(). This is fine but it adds alot of extra boilerplate we need to write – grabbing the model instance, checking if it exists etc. Thankfully Laravel provides something called route model binding that helps with this issue. Instead of providing an $id variable, the method will be given the $project or $task object instance instead.
Open up /app/Http/routes.php and add the following two lines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Provide controller methods with object instead of ID Route::model('tasks', 'Task');Route::model('projects', 'Project'); // Use slugs rather than IDs in URLs Route::bind('tasks', function($value, $route) { return AppTask::whereSlug($value)->first(); }); Route::bind('projects', function($value, $route) { return AppProject::whereSlug($value)->first(); }); Route::resource('projects', 'ProjectsController'); Route::resource('projects.tasks', 'TasksController'); |
and in your TasksController and ProjectsController replace every method definition’s $id reference with Task $task and Project $project like so:
// public function edit($id) public function edit(Project $project) |
Don’t forget to add use AppTask and AppProject at the top of your respective controllers now that we’re referencing those models!
At this point you can also pass the object to its respective view in each controllers show, edit and update methods like so as we’ll be using them later:
1 2 3 4 |
public function edit(Project $project) { return view('projects.show', compact('project'));} |
The TasksController will also need some minor modifications. Because we’re using nested resources, php artisan route:list will tell us that task routes all include a {projects} mask in addition to the {tasks} mask that some of them receive. As a result the controller methods will be passed a Project instance as their first argument. So update them accordingly remembering to update method docs and pass the new $project variable.
By this point your controllers should look like so:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
// /app/Http/Controllers/ProjectsController.php <?php namespace AppHttpControllers; use AppProject; use AppHttpRequests; use AppHttpControllersController; use IlluminateHttpRequest; class ProjectsController extends Controller { /** * Display a listing of the resource. * * @return Response */ public function index() { $projects = Project::all(); return view('projects.index', compact('projects')); } /** * Show the form for creating a new resource. * * @return Response */ public function create() { return view('projects.create'); } /** * Store a newly created resource in storage. * * @return Response */ public function store() { // } /** * Display the specified resource. * * @param AppProject $project * @return Response */ public function show(Project $project) { return view('projects.show', compact('project')); } /** * Show the form for editing the specified resource. * * @param AppProject $project * @return Response */ public function edit(Project $project) { return view('projects.edit', compact('project')); } /** * Update the specified resource in storage. * * @param AppProject $project * @return Response */ public function update(Project $project) { // } /** * Remove the specified resource from storage. * * @param AppProject $project * @return Response */ public function destroy(Project $project) { // } } // /app/Http/Controllers/TasksController.php <?php namespace AppHttpControllers; use AppProject; use AppTask; use AppHttpRequests; use AppHttpControllersController; use IlluminateHttpRequest; class TasksController extends Controller { /** * Display a listing of the resource. * * @param AppProject $project * @return Response */ public function index(Project $project) { return view('tasks.index', compact('project')); } /** * Show the form for creating a new resource. * * @param AppProject $project * @return Response */ public function create(Project $project) { return view('tasks.create', compact('project')); } /** * Store a newly created resource in storage. * * @param AppProject $project * @return Response */ public function store(Project $project) { // } /** * Display the specified resource. * * @param AppProject $project * @param AppTask $task * @return Response */ public function show(Project $project, Task $task) { return view('tasks.show', compact('project', 'task')); } /** * Show the form for editing the specified resource. * * @param AppProject $project * @param AppTask $task * @return Response */ public function edit(Project $project, Task $task) { return view('tasks.edit', compact('project', 'task')); } /** * Update the specified resource in storage. * * @param AppProject $project * @param AppTask $task * @return Response */ public function update(Project $project, Task $task) { // } /** * Remove the specified resource from storage. * * @param AppProject $project * @param AppTask $task * @return Response */ public function destroy(Project $project, Task $task) { // } } |
If you refresh the url /projects/project-1 everything should still be working.
Displaying Our Models
Project listing page
It’s time to start listing our projects and tasks. Open /projects in your browser. Based on php artisan route:list this is our project listing page. Open/resources/views/projects/index.blade.php and set it to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@extends('app') @section('content') <h2>Projects</h2> @if ( !$projects->count() ) You have no projects @else <ul> @foreach( $projects as $project ) <li><a href="{{ route('projects.show', $project->slug) }}">{{ $project->name }}</a></li> @endforeach </ul> @endif @endsection |
There are a few things going on above:
- I’m using the blade templating language’s if and foreach control-flow functions as well as its print function (the double curly braces).
- I’m checking if there are any projects to show. If not, display a message saying so. If there are, list them all
- I’m calling the route() helper with a named route (You can see a list of your named routes with php artisan route:list) to link to each projects details page.
You’ll also need to pass the $projects variable to this view or you’ll get an undefined variable error. Open /app/Http/controllers/ProjectsController.phpand update the index() method to:
1 2 3 4 5 |
public function index() { $projects = Project::all(); return view('projects.index', compact('projects'));} |
Refresh and you’ll now see a listing of your projects.
Model Relations – The Project Details page
On the project details page we need to display a list of the given projects tasks. To do that we need to define a one-to-many relationship in our Projectmodel allowing it to grab its tasks.
Open /app/Project.php and add a tasks() method like so:
1 2 3 4 |
public function tasks() { return $this->hasMany('AppTask'); } |
Inversely we can also add a many-to-one relationship to our Task model:
1 2 3 4 |
public function project() { return $this->belongsTo('AppProject'); } |
To make sure it worked:
$ php artisan tinker >AppProject::whereSlug('project-1')->first()->tasks->count(); 5 >AppProject::whereSlug('project-2')->first()->tasks->count(); 2 >AppTask::first()->project->name; Project 1 |
Perfect! The view can now be updated (/resources/views/projects/show.blade.php):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@extends('app') @section('content') <h2>{{ $project->name }}</h2> @if ( !$project->tasks->count() ) Your project has no tasks. @else <ul> @foreach( $project->tasks as $task ) <li><a href="{{ route('projects.tasks.show', [$project->slug, $task->slug]) }}">{{ $task->name }}</a></li> @endforeach </ul> @endif @endsection |
Click a project on the project listing page in your browser and your project will now display complete with its task listing.
Finally we have the task show page (/resources/views/tasks/show.blade.php). This one is very straightforward:
1 2 3 4 5 6 7 8 9 10 |
@extends('app') @section('content') <h2> {!! link_to_route('projects.show', $project->name, [$project->slug]) !!} - {{ $task->name }} </h2> {{ $task->description }} @endsection |
Note: Be very careful when using model relationships. It’s easy to generate huge numbers of SQL queries if you’re not careful. This is called the N+1 problem. See the video below for details on what exactly the N+1 problem is, and how to protect against it.
Conclusion
Today we covered:
- Route Model Binding
- Models (with one-to-many relationships)
- Controllers (with route model binding)
- Views (with blade templating language and layouts)
We now have a working listing of projects and tasks. In the next lesson we’ll concentrate on editing, creating and deleting projects and tasks. Stay tuned!
Creating a Basic ToDo Application in Laravel 5 – Part 3
Welcome back to my simple to-do application tutorial for Laravel 5. This tutorial is relatively long so I’ve broken it up into multiple posts.
- Part 1 – Installation, Database and Routes
- Part 2 – Listing Projects and Tasks
- Part 3 – Create/Edit/Delete
- Part 4 – Validation
The source for each part can also be found on GitHub.
So far we’ve learned how to install and set up Laravel, set up some projectand task resources and displayed them to the user. In this chapter we’ll learn how to set up create, edit and delete pages/actions.
Before you Begin
If you’re a Laracasts member, watch the videos RESTful Forms, Create/Edit Forms and Form Requests & Controller Validation. These videos explain far better and in far more detail the concepts below. If you’re serious about learning Laravel I’d highly recommend you sign up.
Adding Navigation Links
Before we do anything it would make life a little easier to add create/edit/delete/back links to our projects and tasks pages.
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<!-- /resources/views/projects/index.blade.php --> @extends('app') @section('content') <h2>Projects</h2> @if ( !$projects->count() ) You have no projects @else <ul> @foreach( $projects as $project ) <li> {!! Form::open(array('class' => 'form-inline', 'method' => 'DELETE', 'route' => array('projects.destroy', $project->slug))) !!} <a href="{{ route('projects.show', $project->slug) }}">{{ $project->name }}</a> ( {!! link_to_route('projects.edit', 'Edit', array($project->slug), array('class' => 'btn btn-info')) !!}, {!! Form::submit('Delete', array('class' => 'btn btn-danger')) !!} ) {!! Form::close() !!} </li> @endforeach </ul> @endif <p> {!! link_to_route('projects.create', 'Create Project') !!} </p> @endsection <!-- /resources/views/projects/show.blade.php --> @extends('app') @section('content') <h2>{{ $project->name }}</h2> @if ( !$project->tasks->count() ) Your project has no tasks. @else <ul> @foreach( $project->tasks as $task ) <li> {!! Form::open(array('class' => 'form-inline', 'method' => 'DELETE', 'route' => array('projects.tasks.destroy', $project->slug, $task->slug))) !!} <a href="{{ route('projects.tasks.show', [$project->slug, $task->slug]) }}">{{ $task->name }}</a> ( {!! link_to_route('projects.tasks.edit', 'Edit', array($project->slug, $task->slug), array('class' => 'btn btn-info')) !!}, {!! Form::submit('Delete', array('class' => 'btn btn-danger')) !!} ) {!! Form::close() !!} </li> @endforeach </ul> @endif <p> {!! link_to_route('projects.index', 'Back to Projects') !!} | {!! link_to_route('projects.tasks.create', 'Create Task', $project->slug) !!} </p> @endsection |
For the most part this should be pretty self explanatory. The only tricky concept is the delete link. Resource controllers require a HTTP DELETE method to be sent. This can’t be done with a standard link so a form submit to the given route is required. See Actions Handled by Resource Controller in the documentation for more information.
Creating the Add and Edit pages
With the listing pages all set up, we need to be able to add and edit projects and tasks. The create and edit forms will be pretty much identical so instead of duplicating them, we will inherit from a single form partial for each model.
I’ll begin with the project create/edit views:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!-- /resources/views/projects/create.blade.php --> @extends('app') @section('content') <h2>Create Project</h2> {!! Form::model(new AppProject, ['route' => ['projects.store']]) !!} @include('projects/partials/_form', ['submit_text' => 'Create Project']) {!! Form::close() !!} @endsection <!-- /resources/views/projects/edit.blade.php --> @extends('app') @section('content') <h2>Edit Project</h2> {!! Form::model($project, ['method' => 'PATCH', 'route' => ['projects.update', $project->slug]]) !!} @include('projects/partials/_form', ['submit_text' => 'Edit Project']) {!! Form::close() !!} @endsection |
Do the same for tasks but with updated routes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!-- /resources/views/tasks/create.blade.php --> @extends('app') @section('content') <h2>Create Task for Project "{{ $project->name }}"</h2> {!! Form::model(new AppTask, ['route' => ['projects.tasks.store', $project->slug], 'class'=>'']) !!} @include('tasks/partials/_form', ['submit_text' => 'Create Task']) {!! Form::close() !!} @endsection <!-- /resources/views/tasks/edit.blade.php --> @extends('app') @section('content') <h2>Edit Task "{{ $task->name }}"</h2> {!! Form::model($task, ['method' => 'PATCH', 'route' => ['projects.tasks.update', $project->slug, $task->slug]]) !!} @include('tasks/partials/_form', ['submit_text' => 'Edit Task']) {!! Form::close() !!} @endsection |
Now the are a few new concepts here:
Including a Partial
Firstly you’ll notice my use of blades @include method. This includes the form view that we will define later (for now, just create the files/resources/views/projects/partials/_form.blade.php and/resources/views/tasks/partials/_form.blade.php). Because the same form will be used on both create and edit pages we need its submit button to have a ‘Create Form’ and ‘Edit Form’ message appropriately so a submit_text variable is passed to the view.
Form Model Binding
The forms require different HTML <form> tags so rather those were split out from the _form partial and placed directly in the views. The Add form is a simple POST request to the projects.store named route and the Edit form is a PATCH to projects.update. This may seem confusing but it’s just the way RESTful controllers work.
Also notice the use of Form::model(). This is called called form model binding and though this doesn’t do much now, it will be used to automatically populate the edit form later when we add the fields with using Form::input().
CSRF Protection
Form helpers provide alot of functionality for free. If you go to /projects/create and view page source you’ll see something like the following:
1 2 3 |
<form method="POST" action="http://l4todo.localhost.com/projects" accept-charset="UTF-8"> <input name="_token" type="hidden" value="Y8uOo7SeD5tQZExezDf5a7UwiYR4P6qIHEUKJNxI"> </form> |
See the _token field? This is a CSRF token automatically generated by the {{ Form::model() }} call which prevents cross-site request forgery. Suffice to say it’s a good thing and we didn’t even have to do anything special to get it!
Create the Edit Forms
We need form markup for our projects and tasks. Thanks to form model binding, we can just use Laravel’s Form helpers to output all the fields we need.
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 |
<!-- /resources/views/projects/partials/_form.blade.php --> <div class="form-group"> {!! Form::label('name', 'Name:') !!} {!! Form::text('name') !!} </div> <div class="form-group"> {!! Form::label('slug', 'Slug:') !!} {!! Form::text('slug') !!} </div> <div class="form-group"> {!! Form::submit($submit_text, ['class'=>'btn primary']) !!} </div> <!-- /resources/views/tasks/partials/_form.blade.php --> <div class="form-group"> {!! Form::label('name', 'Name:') !!} {!! Form::text('name') !!} </div> <div class="form-group"> {!! Form::label('slug', 'Slug:') !!} {!! Form::text('slug') !!} </div> <div class="form-group"> {!! Form::label('completed', 'Completed:') !!} {!! Form::checkbox('completed') !!} </div> <div class="form-group"> {!! Form::label('description', 'Description:') !!} {!! Form::textarea('description') !!} </div> <div class="form-group"> {!! Form::submit($submit_text) !!} </div> |
That’s about it. In your browser you should now be able to browse to your add and edit pages. How easy was that!
Making the Forms Work
We have project and task add and edit forms displaying and a pseudo-form for deleting them. Now to make everything work as advertised.
Firstly add to the top of your controllers:
1 2 |
use Input; use Redirect; |
Now for the store, update and destroy methods.
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 46 47 48 |
// ProjectsController public function store() { $input = Input::all(); Project::create( $input ); return Redirect::route('projects.index')->with('message', 'Project created'); } public function update(Project $project) { $input = array_except(Input::all(), '_method'); $project->update($input); return Redirect::route('projects.show', $project->slug)->with('message', 'Project updated.'); } public function destroy(Project $project) { $project->delete(); return Redirect::route('projects.index')->with('message', 'Project deleted.'); } // TasksController public function store(Project $project) { $input = Input::all(); $input['project_id'] = $project->id; Task::create( $input ); return Redirect::route('projects.show', $project->slug)->with('message', 'Task created.'); } public function update(Project $project, Task $task) { $input = array_except(Input::all(), '_method'); $task->update($input); return Redirect::route('projects.tasks.show', [$project->slug, $task->slug])->with('message', 'Task updated.'); } public function destroy(Project $project, Task $task) { $task->delete(); return Redirect::route('projects.show', $project->slug)->with('message', 'Task deleted.'); } |
Again the above is pretty much self explanatory and boilerplate-free thanks to route model binding and Laravel’s beautiful expressive syntax.
If you submitted one of the forms now, you’d likely see an errorMassAssignmentException: _token. A mass assignment is where you pass an array of data to Model::create() or Model::update() the way we’re doing in our controllers, instead of setting one field at a time. To fix this simply add an empty guarded property to each model.
1 2 3 4 5 6 7 8 |
class Project extends Model { protected $guarded = []; class Task extends Model { protected $guarded = []; |
Flash Messages
One thing to note from the above is my use of the with() function. with()passes a flash (one time use) variable to the session which can then be read on the next page load. We now need to check for that message and display it to the user. Open /resources/views/app.blade.php and add the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... <div class="content"> @if (Session::has('message')) <div class="flash alert-info"> <p>{{ Session::get('message') }}</p> </div> @endif @yield('content') </div> ... |
Try creating a project. The message will display as it should however refresh the page and it will be gone.
Conclusion
Today things got a little more interesting. We added create and edit forms and delete functionality, we learned about CSRF and Form Model Binding and even a few more blade and Eloquent functions.
With everything now working, have a play around. Create, edit and delete your resources. Remember you can always php artisan migrate:refresh –seed when you’re done and your database will be instantly reset.
In the next and final lesson we’ll put on the finishing touches including form validation and slug management. Stay tuned!
Creating a Basic ToDo Application in Laravel 5 – Part 4
Welcome back to my simple to-do application tutorial for Laravel 5. This tutorial is relatively long so I’ve broken it up into multiple posts.
- Part 1 – Installation, Database and Routes
- Part 2 – Listing Projects and Tasks
- Part 3 – Create/Edit/Delete
- Part 4 – Validation
The source for each part can also be found on GitHub.
So far we’ve learned how to install and set up Laravel, set up some project and task nested resources and display them to the user. Create, edit and delete functionality has also been implemented. In this chapter we’ll finish things off by adding form validation.
Server Side Form Validation
As it stands our create and edit forms work but they’re not validated. Let’s fix that.
There are multiple ways of handling form validation – some objectively better than others, however for such a small project as this I like to use the controllers validate() method with a IlluminateHttpRequest object like so:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
// /app/Http/Controllers/ProjectsController.php use IlluminateHttpRequest; class ProjectsController extends Controller { protected $rules = [ 'name' => ['required', 'min:3'], 'slug' => ['required'], ]; /** * Store a newly created resource in storage. * * @param IlluminateHttpRequest $request * @return Response */ public function store(Request $request) { $this->validate($request, $this->rules); $input = Input::all(); Project::create( $input ); return Redirect::route('projects.index')->with('message', 'Project created'); } /** * Update the specified resource in storage. * * @param AppProject $project * @param IlluminateHttpRequest $request * @return Response */ public function update(Project $project, Request $request) { $this->validate($request, $this->rules); $input = array_except(Input::all(), '_method'); $project->update($input); return Redirect::route('projects.show', $project->slug)->with('message', 'Project updated.'); } // /app/Http/Controllers/TasksController.php use IlluminateHttpRequest; class TasksController extends Controller { protected $rules = [ 'name' => ['required', 'min:3'], 'slug' => ['required'], 'description' => ['required'], ]; /** * Store a newly created resource in storage. * * @param AppProject $project * @param IlluminateHttpRequest $request * @return Response */ public function store(Project $project, Request $request) { $this->validate($request, $this->rules); $input = Input::all(); $input['project_id'] = $project->id; Task::create( $input ); return Redirect::route('projects.show', $project->slug)->with('Task created.'); } /** * Update the specified resource in storage. * * @param AppProject $project * @param AppTask $task * @param IlluminateHttpRequest $request * @return Response */ public function update(Project $project, Task $task, Request $request) { $this->validate($request, $this->rules); $input = array_except(Input::all(), '_method'); $task->update($input); return Redirect::route('projects.tasks.show', [$project->slug, $task->slug])->with('message', 'Task updated.'); } |
We need a place to display any generated errors. Open/resources/views/app.blade.php and drop the following above @yield(‘content’):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<div class="content"> @if (Session::has('message')) <div class="flash alert-info"> <p>{{ Session::get('message') }}</p> </div> @endif @if ($errors->any()) <div class='flash alert-danger'> @foreach ( $errors->all() as $error ) <p>{{ $error }}</p> @endforeach </div> @endif @yield('content') </div> |
See Available Validation Rules for a complete list of rules available. Validation should now be working. If validation on a form fails, $this->validate() will redirect back to the current page along with an ErrorBag of errors which will then be displayed on the page.
Conclusion
Over the court of this tutorial we’ve learned how to install and configure Laravel 5, add extra packages, handle seeds and migrations, generate and display resources, add create/edit/display pages for those resources and finally today we set up form validation. Along the way we even picked up a few more advanced concepts such as route model binding and CSRF protection. Despite its seemingly simple appearance at first a to-do application generally makes for a great starter app for any new PHP framework and Laravel has made the process of creating ours painless and fun. Happy coding!