First install Composer, then:
$ composer create-project laravel/laravel <project-name>
After that we can start a webserver, and test the default index page:
$ php artisan serve
$ vagrant box add laravel/homestead
$ composer global require "laravel/homestead=~2.0"
$ homestead init # create homestead configuration file
$ homestead edit # edit the homestead configuration
If you can't use the homestead
command, do this:
# MAC OS
$ vi ~/.bash_profile
# export PATH="~/.composer/vendor/bin:$PATH"
$ source ~/.bash_profile # reload bash
# DEBIAN
$ vi ~/.profile
# export PATH="~/.composer/vendor/bin:$PATH"
$ source ~/.profile # reload bash
If you don't have SSH Keys, you can generate them with:
$ ssh-keygen -t rsa -C "[email protected]"
To configure the list of development domains, you can edit them here:
$ sudo vi /etc/hosts
To start your virtual machine you have to call:
$ homestead up
To enter your homestead environment:
$ homestead ssh
To reprovision the projects, without loosing the DBs:
$ homestead edit
$ homestead halt
$ homestead up --provision
To connect to your development database, use these:
- Host: 127.0.0.1
- Port: 33060
- Username: homestead
- Password: secret
$ touch storage/database.sqlite
$ composer require doctrine/dbal
$ php artisan make:controller PagesController
$ php artisan make:controller PagesController --plain
$ php artisan make:migration create_articles_table --create="articles" # create
$ php artisan make:migration add_excert_to_articles_table --table="articles" # modify
$ php artisan make:model Article
In Laravel 5, the HTML package is not installed by default. This is the one that is used to create forms in blade.
$ composer require illuminate/html
Then we have to add its Service Provider and its Facade to the app.php
file.
At the receiving route, you have to do the following:
// read all inputs
$input = Request::all();
// read some input
$input = Request::get('title');
{!! Form::model($article, ['method' => 'PATCH', 'action' => ['ArticlesController@update', $article->id]]) !!}
// change open to model, and the first parameter should be the item returned from the DB
protected $dates = ['published_at'];
// the dates listed in this variable, are automatically converted to a Carbon object
public function scopePublished($query)
{
$query->where('published_at', '<=', Carbon::now());
}
Also, you can pass variables, after the $query
parameter:
public function scopePublished($query, $date)
{
$query->where('published_at', '<=', $date);
}
public function setPublishedAtAttribute($date)
{
$this->attributes['published_at'] = Carbon::parse($date);
}
// also can be used to encrypt the password, and not doing it manually over and over at every place
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password); // or Hash::make($password)
}
public function getPublishedAtAttribute($date)
{
return Carbon::parse($date)->format('Y-m-d');
}
// for this relationship we have to add a migration, so the Article has a user_id attribute
// $table->integer('user_id')->unsigned(); // a positive value
// $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); // optional, this deletes the articles of deleted users
public function articles()
{
return $this->hasMany('Article');
}
public function user()
{
return $this->belongsTo('User');
}
With these relationships, we can use Laravel logic to create a new article, and make a user its owner:
$article = new Article($request->all());
Auth::user()->articles()->save($article);
// in this fashion, we don't have to set the user_id property as fillable
public function tags()
{
return $this->belongsToMany('App\Tag');
}
// migration to create a pivot table
Schema::create('article_tag', function(Blueprint $table)
{
$table->integer('article_id')->unsigned()->index();
$table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->timestamps();
});
$article = Auth::user()->articles()->create($request->all());
$article->tags()->sync($requests->input('tags')); // make a many to many relationship
// we can also keep using attach and dettach, but they are for add and remove relations
// if we want to say "forget about the old relations, and use this ones" we'll use sync
To read the associated tags at our article edit form, we can do the following:
// Article.php
public function getTagListAttribute()
{
return $this->tags->lists('id');
}
// redirect
return redirect('articles');
$ php artisan make:request CreateArticleRequest
This will create a class that has two methods: authorize and rules.
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required|min:3',
'body' => 'required',
'published_at' => 'required|date,
];
}
At the controller that is going to perform a validation, we have to typehint the request at the desired action.
public function store(Requests\CreateArticleRequest $request)
{
// now the method is going to be accessed only if the validation passes
Article::create($request->all()); // we can use this request now, and it's a lot cleaner
// ... //
}
{{ $errors }} // we always have an erros variable in our views
@if ($errors->any())
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
@endif
The error messages can be translated in the language file: resources/lang/xx/validation.php
.
// it can be at each controller
public function __construct()
{
// this only verifies at the create method
$this->middleware('auth', ['only' => 'create']);
// this will only permits guests at the index method
$this->middleware('auth', ['except' => 'index']);
}
// also it can be at a route definition
Route::get('about', ['middleware' => 'auth', 'uses' => 'PagesController@about']);
If we want to create our own middleware:
$ php artisan make:middleware RedirectIfNotAManager
Then we will have to add it to the /app/Http/Kernel.php
file. There's two arrays: $middleware
(they will run for every route) and $routeMiddleware
(they will run for specific routes).
Always add return $next($request);
at the end of the handle()
method.
They are like the old filters in Laravel 4.
@include ('errors.list')
// this should be a blade file hosted in the views folder
@include ('articles._form', ['submitButtonText' => 'Add Article'])
// also you can pass data to the view partial
// app/Providers/RouterServiceProvider.php
public function boot(Router $router)
{
parent:boot($router);
// for each route key we have to bind the model with each one
$router->model('articles', 'App\Article');
// another way, if we want to add some filtered results
$router->bind('articles', function($id)
{
return \App\Article::published()->findOrFail($id);
});
}
// app/Http/routes.php
Route::resource('articles', 'ArticlesController');
// app/Http/Controllers/ArticlesController.php
public function show(Article $article)
{
return view('articles.show', compact('article'));
}
Another way to route bind the model at the RouterServiceProvider, and add more clauses to the query:
$router->bind('articles', function($id)
{
return \App\Article::published()->findOrFail($id);
});
// app/Http/routes/lessons.php
Route::get('lessons', [
'as' => 'lessons',
'uses' => 'LessonsController@lessons'
]);
// app/Http/routes.php
foreach (File::allFiles(__DIR__.'/routes/') as $partial) {
require_once $partial->getPathname();
}
Laravel 5 comes with a file called package.json
. This is a NodeJS dependencies file, because that's what Laravel uses (Gulp and Laravel Elixir).
$ npm install
And now we can work with the file called gulpfile.js
.
If we prefer Sass over Less, we should change it to this:
elixir(function(mix) {
mix.sass('app.scss');
});
Now we can work at the resources/assets/sass/app.scss
file.
To compile that file, we can type $ gulp
at the root of our project. If we don't want to manually compile our Sass files every time, we can type $ gulp watch
and Gulp will autocompile the styles every time we save the file with new modifications.
If we want to use CoffeeScript, we can do it updating the gulpfile.js
file: mix.sass('app.scss').coffee();
, and we can work with .coffee
files at resources/assets/coffee/
folder.
To make a single .css
file so it will be only one http request, we can do this in gulpfile.js
:
mix.styles([
'vendor/normalize.css',
'app.css'
], 'public/output/styles.css', 'public/css');
// if we use 'null' at the second parameter, it will generate a file called 'all.css'
If we also want to make a minified css file, we have to call $ gulp --production
. Just like that.
This is the same-ish code to script files.
mix.scripts([
'vendor/jquery.js',
'main.js',
'coupon.js'
], 'public/output/scripts.js', 'public/js');
Another way to minify:
mix.scriptsIn('public/js');
// the problem with this is that you can't define the order of files that will be added
Also, if we want to run our tests, we can add it to our Gulp file.
mix.phpUnit();
// also we can do mix.phpUnit().phpSpec(); if we want to use PHPSpec
With this in mind, we can type $ gulp tdd
.
mix.sass('app.scss');
mix.styles([ 'vendor/normalize.css', 'app.css' ], null, 'public/css');
mix.version('public/css/all.css');
In this matter, we are going to be versioning the all.css
file, and we can get something like all-0d57812f.css
. But, how can we call this "new" css file at our views? We have to update our layout file to this:
<link ref="stylesheet" href="{{ elixir('css/all.css') }}">
We can verify that this is working at the rev-manifest.json
file.
$ composer require laracasts/flash
This is for use at a custom partial view, so it can have a variable every time is called to display:
Create a View Composer Service Provider:
$ php artisan make:provider ViewComposerServiceProvider
// app/Providers/ViewComposerServiceProvider.php
public function boot()
{
$this->composeNavigation();
// it's better to separate every view composer, so our boot() method doesn't get bloated and messy
}
public function composeNavigation()
{
view()->composer('partials.nav', 'App\Http\Composers\NavigationComposer');
}
// app/Http/Composers/NavigationComposer.php
<?php namespace App\Http\Composers;
use Illuminate\Contracts\View\View;
class NavigationComposer {
public function compose(View $view)
{
$view->with('latest', \App\Article::latest()->first());
}
}
After this, we have to add this Service Provider to the app.php
file, at the providers
list.
$ php artisan fresh
$ php artisan clear-compiled
Install dependencies:
$ composer require laracasts/generators --dev
$ composer require laracasts/testdummy --dev
Create migration, model, migrate and create seed:
$ php artisan make:migration:schema create_posts_table --schema="user_id:integer:foreign, title:string, body:text"
$ php artisan migrate
$ php artisan make:seed posts
Configure seeds:
// database/seeds/PostsTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use Laracasts\TestDummy\Factory as TestDummy;
class PostsTableSeeder extends Seeder {
public function run()
{
TestDummy::times(50)->create('App\Post');
}
}
// database/seeds/DatabaseSeeder.php
public function run()
{
Model::unguard();
$this->call('PostsTableSeeder');
}
// tests/Factories/all.php
<?php
$factory('App\User', [
'name' => $faker->name,
'email' => $faker->email,
'password' => $faker->word
]);
$factory('App\Post', [
'user_id' => 'factory:App\User',
'title' => $faker->sentence,
'body' => $faker->paragraph
]);
Run seeds to populate the DB:
$ php artisan db:seed
event('ArticleCreated', $article);