Skip to content

Instantly share code, notes, and snippets.

@alemohamad
Last active November 12, 2017 01:04
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save alemohamad/46bd6efef7c67032f0de to your computer and use it in GitHub Desktop.
Save alemohamad/46bd6efef7c67032f0de to your computer and use it in GitHub Desktop.

Install Laravel 5 through Composer

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

Install Laravel Homestead 2.0

$ 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 "your@email.com"

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

Database

To connect to your development database, use these:

  • Host: 127.0.0.1
  • Port: 33060
  • Username: homestead
  • Password: secret

Create an SQLite database

$ touch storage/database.sqlite

Manage migrations with SQLite

$ composer require doctrine/dbal

Artisan commands

Controllers

$ php artisan make:controller PagesController
$ php artisan make:controller PagesController --plain

Migrations

$ php artisan make:migration create_articles_table --create="articles" # create
$ php artisan make:migration add_excert_to_articles_table --table="articles" # modify

Models

$ php artisan make:model Article

Forms

Install the HTML package

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.

Get form input values

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 Binding

{!! 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

Eloquent

Dates

protected $dates = ['published_at'];
// the dates listed in this variable, are automatically converted to a Carbon object

Scope methods

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);
}

SetAttribute methods (saving)

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)
}

GetAttribute methods (reading)

public function getPublishedAtAttribute($date)
{
    return Carbon::parse($date)->format('Y-m-d');
}

Relationships

// 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

Pivot relationships

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');
}

Common actions

// redirect
return redirect('articles');

Validation

$ 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
    // ... //
}

Validation errors

{{ $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.

Middleware

// 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.

View Partials

@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

Routes

Route Model Binding

// 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);
});

Route partials

// 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();
}

Asset Management

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.

Sass + CoffeeScript

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

Automated local tests

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.

Versioning files

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.

Flash Messaging

$ composer require laracasts/flash

View composer

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.

Remove the scaffolding included with the framework

$ php artisan fresh

Remove the compiled class file

$ php artisan clear-compiled

Generators and Seeders

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

Fire events

event('ArticleCreated', $article);

Contents

  1. Laravel packages
  2. Testing
  3. Server Tools
  4. Markdown
  5. Laravel Homestead
  6. Setup Laravel 4 in a Shared Hosting with securing Laravel base file
  7. Laravel Application Environments
  8. Install ImageMagick
  9. Hooking up a custom domain name to DigitalOcean
  10. Laravel Repository (IoC)
  11. Testing in Laravel
  12. Front stuff
  13. Laravel Books
  14. Cloud Hosting Services
  15. Git Hosting Services
  16. Online Playgrounds
  17. NodeJS
  18. Other

Laravel packages

Laravel 5

Other packages

Testing

Server Tools

Docker

  • Install VirtualBox
  • Install Docker
  • Restart your machine
  • In Terminal run $ boot2docker init
  • To use docker run $ boot2docker up (to turn it off run $ boot2docker down)
    • We can do the last 2 commands opening the boot2docker app that we installed in our Mac
  • To find out info about docker images in our machine run $ docker images
  • To find out info about docker processes in our machine run $ docker ps -all
  • Docker for the Laravel framework

Markdown

Laravel Homestead

# 1. install Vagrant and VirtualBox
# https://www.vagrantup.com/
# https://www.virtualbox.org/

# 2. add homestad box into vagrant
$ vagrant box add laravel/homestead

# 3. create Code folder
$ mkdir Code
$ cd Code

# 4. clone the homestead repository
$ git clone https://github.com/laravel/homestead.git homestead

# 5. prepare your yaml config file
$ cd homestead
$ vim Homestead.yaml

# 6. if you haven't, create your keys (this will be used in authorize and keys in the yaml file) (optional)
$ ssh-keygen -t rsa -C "your@email.com"

# 7. update your hosts file with your custom url
$ sudo vi /etc/hosts

# 8. start your virtual machine in the homestead folder
$ vagrant up

#################
# OTHER ACTIONS #
#################

# visit the website: http://example.app:8000/

# enter the VM in the homestead folder
$ vagrant ssh

# suspend the VM before turning off your computer
$ vagrant suspend
# and then you can do "vagrant up" to use the VM again

# to reload the VM in the homestead folder
$ vagrant reload

# to add a virtual host into homestead VM
## Easy method
$ vagrant reload --provision
## Advanced method
$ vagrant ssh
$ serve new-domain.app /home/vagrant/Code/path/to/public/directory

# to update the version of homestead
$ vagrant box update

# create a new laravel project
$ composer create-project laravel/laravel <project-name>

# create a new laravel project with a custom version
$ composer create-project laravel/laravel=4.2.* <project-name> --prefer-dist

# change permissions of storage folder
$ sudo chmod -R 777 app/storage

# play with artisan, so we don't have to create/edit php files
$ php artisan tinker

# copy the ssh public key
$ pbcopy < ~/.ssh/id_rsa.pub

Use HHVM in Laravel Homestead

Setup Laravel 4 in a Shared Hosting with securing Laravel base file

# 0. This assume that you have file structure something like this:
/home/username/public_html

# 1. Create new folder outside public_html:
/home/username/main-laravel

# 2. Move all the main file of Laravel (app, boostrap, vendor, composer.json, composer.lock, phpunit.xml etc) into that folder (step 1) **except Public folder

# 3. Open /home/username/main-laravel/bootstrap/paths.php and edit to look like this:
## replace
'app' => __DIR__.'/../app',
'public' => __DIR__.'/../public',
'base' => __DIR__.'/..',
'storage' => __DIR__.'/../app/storage',
## to
'app' => __DIR__.'/../../main-laravel/app',
'public' => __DIR__.'/../../public_html/laravel',
'base' => __DIR__.'/../../main-laravel',
'storage' => __DIR__.'/../../main-laravel/app/storage',

# 4. Now create a new folder inside public_html
/home/username/public_html/laravel

# 5. Now, move all the content in public folder of Laravel into that folder (step 4)

# 6. Open /home/username/public_html/laravel/index.php and edit to look like this:
## replace
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/start.php';
## to
require __DIR__.'/../../main-laravel/bootstrap/autoload.php';
$app = require_once __DIR__.'/../../main-laravel/bootstrap/start.php';

# 7. Now create .htaccess in /home/username/public_html and insert this code:
RewriteEngine on
RewriteCond %{REQUEST_URI} !^laravel
RewriteRule ^(.*)$ laravel/$1 [L]

# Now, your laravel website can be access at http://username.com

Laravel Application Environments

bootstrap/start.php

$env = $app->detectEnvironment( function ()
{
    return getenv('APP_ENV') ?: 'development';
});

.env.development.php

<?php

return [
    'DB_PASSWORD' => 'secret password'
];

// add this file to .gitignore

// .env.php has the production environemnt keys (it exists only in production)
// (if the hosting can't define env vars, this should be the way)

How to retrieve each env variables in our project?

getenv('DB_PASSWORD');

Install ImageMagick

Just ssh into your vm, then install the required packages, like so:

vagrant ssh
sudo apt-get update
sudo apt-get install imagemagick
sudo apt-get install php5-imagick

Hooking up a custom domain name to DigitalOcean

Hostname Record Type Value
@ A [ip-address]
  •    | A           | [ip-address]
    

www | CNAME | [url-address]

  1. The main domain (it can be the alias).
  2. An alias to point every subdomain to the ip-address.
  3. Point a subdomain to the main domain.

If you can't do advanced DNS config

  1. Go to https://cloud.digitalocean.com/domains
  2. Click on “Add Domain”
  3. Add the domain in the “Name” field
  4. Add the IP in the “IP Address” field
  5. Select the droplet where you have that domain with that IP address
  6. Click on “CREATE DOMAIN”, and that's it.
  7. Then add the DigitalOcean DNS in your domain provider DNS config
ns1.digitalocean.com
ns1.digitalocean.com
ns1.digitalocean.com

Laravel Repository (IoC)

app/Acme/LessonRepositoryInterface.php

<?php namespace Acme;

interface LessonRepositoryInterface {
  public function recent();
}

app/Acme/DbLessonRepository.php

<?php namespace Acme;

class DbLessonRepository implements LessonRepositoryInterface {
  public function recent()
  {
    return Lessons::take(5)->orderBy('id', 'desc')->get();
    // you can also add this as a method inside the Lessons model
  }
}

Add respository files to Composer

"autoload": {
  "classmap": {
    "app/Acme"
  }
}

Then do a composer dump.

app/routes.php

<?php

App::bind('Acme\LessonRepositoryInterface', 'Acme\DbLessonRepository');
Route::resource('lessons', 'LessonsController');

app/controllers/LessonsController.php

<?php

use Acme\LessonRepositoryInterface as LessonRepository;

class LessonsController extends BaseController {
  
  protected $lesson;
  
  public function __construct(LessonRepository $lesson)
  {
    $this->lesson = $lesson;
  }
  
  public function index()
  {
    return $this->lesson->recent();
  }
  
  // ...
}

Testing in Laravel

Don't always extend TestCase

class ExampleTest extends PHPUnit_Framework_TestCase {
  // use only PHPUnit for business logic
}

class ExampleTest extends TestCase {
  // use complete Laravel TestCase logic
}

Do an acceptance test with Laravel

<?php

class ExampleTest extends TestCase {
  public function test_displays_home_page()
  {
    $this->call('GET', '/');
    $this->see('Hello world', 'h1');
  }
  
  protected function see($text, $element = 'body')
  {
    $crawler = $this->client->getCrawler();
    $found = $crawler->filter("{$element}:contains('{$text}')");
    
    $this->assertGreaterThan(0, count($found), "Expected to see {$text} within the view");
  }
}

Test a controller that use a repository (IoC)

<?php

class LessonsControllerTest extends TestCase {
  
  public function setUp()
  {
    parent::setUp();
    
    // prepare the repository mock for use in every controller test
    $this->lesson = Mockery::mock('Acme\LessonRepositoryInterface');
    App::instance('Acme\LessonRepositoryInterface', $this->lesson);
  }
  
  public function tearDown()
  {
    Mockery::close();
  }
  
  public function test_binds_lessons_to_index_view()
  {
    $this->lesson->shouldReceive('recent')->once()->andReturn('foo');
    
    $result = $this->call('GET', '/lessons');
    
    $this->assertEquals('foo', $result->getContent());
  }
  
}

Improve performance with test database in memory

You can add a configuration file in app/config/testing/database.php:

<?php

return [
  'default' => 'sqlite',
  'connections' => array(
    'sqlite' => array(
      'driver' => 'sqlite',
      'database' => ':memory',
      'prefix' => '',
    )
  )
];

And then create an example test that uses a test database in memory:

<?php

class ExampleTest extends TestCase {
  
  public function setUp()
  {
    parent::setUp();
    Artisan::call('migrate');
  }
  
  public function test_it_works()
  {
    // in orther for this test to work
    // we have to create an Order model and its migration :)
    Order::create(['name' => 'Wii U']);
    
    $this->assertsEquals('Wii U', Order::first()->name);
  }
  
}

Front stuff

Laravel Books

Cloud Hosting Services

Git Hosting Services

Online Playgrounds

NodeJS

SSL certificates

Other

Lumen

Install Lumen through Composer

$ composer create-project laravel/lumen <project-name>

Create database files (by default Lumen doesn't use a database)

$ php artisan make database

Then we can create a migration:

$ php artisan make:migration create_users_table --create=users

For this to work, we have to add then to our composer.json file:

...
"autoload": {
    "classmap": [
        "database/"
    ]
}
...

Then we regenerate our autoloading files:

$ composer du

Then we have to uncomment the following lines at the bootstrap/app.php file:

//...

Dotenv::load(__DIR__.'/../');

//...

$app->withFacades();

$app->withEloquent();

//...

Now we can run our migration:

$ php artisan migrate --force

Taylor's video introducing Lumen

Introducing Lumen @ Laracasts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment