Laravel 101 – Artisan – Outputting Data



Laravel 101 – Artisan – Outputting Data

6 5


laravel-101-slides

Laravel 101 presentation for the Winnipeg PHP Meetup group

On Github cviebrock / laravel-101-slides

Laravel 101

Winnipeg PHP Meetup - March 26, 2014

Colin Viebrock

Laravel

A free, open source, MVC web-application framework

2011 (v1.0) — current (v4.1.24)

Package-based dependency management

laravel.com

Getting Started

Prerequisites

  • PHP 5.4.x
  • Mcrypt extension
  • JSON extension
  • Composer getcomposer.org and install it globally!

Your First Project

Starting a New Project

composer create-project laravel/laravel my-project --prefer-dist
	
git clone git@github.com:laravel/laravel.git my-project
cd my-project
composer install
	
curl -L -O https://github.com/laravel/laravel/archive/master.zip
unzip master.zip
mv laravel-master my-project
cd my-project
composer install
	
composer create-project laravel/laravel my-project --prefer-dist

Git it under control

I see what you did.

Project Structure

Project Folders

  • app < your stuff
  • bootstrap < startup stuff
  • public < static stuff: img, js, css, etc.
  • composer.json < other people's stuff you want
  • vendor < other people's stuff

Application Folders

  • app
    • commands
    • config
    • controllers
    • database
    • lang
    • models
    • start
    • storage
    • tests
    • views
    • filters.php
    • routes.php

Artisan

Artisan

Command line tool to make working with Laravel easier.

php artisan
		

Built-in Server

php artisan serve --port 8000
		
php artisan serve localhost:8000

Routing

Basic Routing

 app/routes.phpRoute::get('/', function() {
  return 'Hello World!';
});
	
git checkout v2.0 localhost:8000

Route Parameters

 app/routes.php// pass parameters via URI

Route::get('/hello/{name}', function($name)
{
  return 'Hello, ' . $name;
});
		
 app/routes.php// escape output for safety

Route::get('/hello/{name}', function($name)
{
  return 'Hello, ' . e($name);
});
		
 app/routes.php// filter the URI data

Route::get('/hello/{name}', function($name)
{
  return 'Hello, ' . e($name);
})->where('name','[A-za-z]+');
		
 app/routes.php// make the param optional

Route::get('/hello/{name?}', function($name='stranger')
{
  return 'Hello, ' . e($name);
})->where('name','[A-za-z]+');
		
git checkout v2.1 /hello/Colin

Listing Routes

php artisan routes
	
php artisan routes

Controllers

Controller Routing

 app/routes.phpRoute::get('hello/{name?}', 'HelloController@showWelcome');
	
 app/controllers/HelloController.phpclass HelloController extends BaseController {

  public function showWelcome($name='stranger')
  {
    return 'Hello, '. e($name) . '!';
  }

}
	
git checkout v3.0 /hello/colin

RESTful Controllers

 app/routes.phpRoute::controller('users', 'UsersController');
	
 app/controllers/UsersController.phpclass UsersController extends BaseController {

  public function getIndex() {
      // GET http://domain.com/users
  }

  public function postProfile() {
      // POST http://domain.com/users/profile
  }

}
	
git checkout v3.0 app/routes.php app/controllers/UsersController.php

Resource Controllers

php artisan controller:make PostsController
	
 app/routes.phpRoute::resource('posts', 'PostsController');
	
 app/controllers/PostsController.phpclass PostsController extends BaseController {

  public function index() {}
  public function create() {}
  public function store() {}
  public function show($id) {}
  public function edit($id) {}
  public function update($id) {}
  public function destroy($id) {}

}
	
php artisan routes
	
php artisan controller:make PostsController app/routes.php app/controllers/PostsController.php php artisan routes

Views

Passing Data to Views

 app/controller/HelloController.phpclass HelloController extends BaseController {

  public function showWelcome($name='stranger')
  {
    return View::make('hello', array('name'=>$name) );
  }

}
		
 app/controller/HelloController.phpclass HelloController extends BaseController {

  public function showWelcome($name='stranger')
  {
    return View::make('hello', compact('name') );
  }

}
		
 app/views/hello.php<html>
  <body>
    <p>Hello, <?php echo e($name); ?>!</p>
  </body>
</html>
		
 app/views/hello.blade.php<html>
  <body>
    <p>Hello, {{{ $name }}}!</p>
  </body>
</html>
		
git checkout 4.0 app/controllers/HelloController.php app/views/hello.blade.php

Blade Templating

Outputting Data

 app/views/VIEWNAME.blade.php<html>
  <body>
    // echo and escape output
    Hello {{ $name }}!  Hello {{{ $html }}}!
  </body>
</html>
	 

If Statements

 app/views/VIEWNAME.blade.php@if( $name=='Bob' )
  Good to see you, Bob!
@elseif( $name=='Mary' )
  Howya doing, Mary!
@else
  Hey, where did Bob go?
@endif

@unless( $signed_in )
  You are not signed in.
@endunless
	 

Loops

 app/views/VIEWNAME.blade.php@for ($i = 0; $i < 10; $i++)
  The current value is {{ $i }}
@endfor

@foreach ($users as $user)
  <p>This is user {{ $user->id }}</p>
@endforeach

@while (true)
  <p>Make it stop!</p>
@endwhile
		

Layouts

 app/views/layouts/master.blade.php<html>
  <body>
    @include('header')  {{-- app/views/header.blade.php --}}

    @section('sidebar')
      This is the master sidebar.
    @show

    <div class="container">
      @yield('content', 'Default content')
    </div>
  </body>
</html>
		
 app/views/VIEWNAME.blade.php@extends('layouts.master')

@section('sidebar')
  @parent
  <p>This is appended to the master sidebar.</p>
@stop

@section('content')
  <p>This is my body content.</p>
@stop
		
git checkout v4.1

Database

Supported Databases

Out-of-the-box support for:

  • MySQL
  • Postgres
  • SQLite
  • SQL Server

Configuration

 app/config/database.phpreturn array(
  'default' => 'mysql',

  'connections' => array(
    'mysql' => array(
      'driver'    => 'mysql',
      'host'      => 'localhost',
      'database'  => 'my-project',
      'username'  => 'db_user',
      'password'  => 's3creT_pa5sw0rD'
      'charset'   => 'utf8',
      'collation' => 'utf8_unicode_ci',
      'prefix'    => '',
    ),
  …
		
 app/config/database.phpreturn array(
  'default' => 'mysql',

  'connections' => array(
    'mysql' => array(
      'driver'    => 'mysql',
      'host'      => 'localhost',
      'database'  => 'my-project',
      'username'  => $_ENV['MYSQL_USER'],
      'password'  => $_ENV['MYSQL_PASS'],
      'charset'   => 'utf8',
      'collation' => 'utf8_unicode_ci',
      'prefix'    => '',
    ),
  …
		

Secret Environment Variables

 .env.phpreturn array(
  'MYSQL_USER' => 'dbuser',
  'MYSQL_PASS' => 's3creT_pa5sw0rD',
);
		
git checkout v5.0

Migrations

Preparing

php artisan migrate:install

php artisan migrate:make create_posts_table --create="posts"
		
php artisan migrate:install php artisan migrate:make create_posts_table --create="posts"

Migration File

 app/database/migrations/###_create_posts_table.phpuse Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    //
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    //
  }

}
		

Schema Building

public function up()
{
  Schema::create('posts', function(Blueprint $table) {
    $table->increments('id');
    $table->string('title', 50);
    $table->text('text')->nullable();
    $table->boolean('active')->default(true);
    $table->timestamps();  // created_at and updated_at
  });
}
public function down()
{
  Schema::drop('posts');
}
		

Run Migrations

php artisan migrate
		

Ooops!

php artisan migrate:rollback
			
SQL

Models

Eloquent

 app/models/Post.phpclass Post extends Eloquent {}
	

Create a New Model

$post = Post::create(
  array(
    'title'  => 'My First Post',
    'text'   => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit',
    'active' => true,
  )
);
		
git checkout v5.1 app/routes.php /make-post SQL

Retrieving

$post = Post::find(1);
$post = Post::findOrFail(1);
$posts = Post::where('id','>',3)->get();
$post = Post::where('title','LIKE','laravel')->first();
$posts = Post::all();
		

Using

$post = Post::find(1);

echo $post->title;
echo $post->created_at;		// uses Carbon package for date handling
$posts = Post::all();

foreach( $posts as $post )
{
  echo $post->title;
}
		

Deleting

$post = Post::find(1);
$post->delete();
Post::destroy(1);
Post::destroy(1,2,3);
$lastWeek = Carbon::now()->subWeek();
Post::where('created_at','<', $lastWeek)->delete();
		

Accessors & Mutators

Accessors

class Post extends Eloquent {

    public function getTitleAttribute($value)
    {
        return ucwords($value);
    }

}
$post = Post::create(array(
  'title' => 'my first post'
));

echo $post->title;		// "My First Post"
		

Mutators

class Post extends Eloquent {

    public function setTextAttribute($value)
    {
        $value = trim($value);
        $this->attributes['text'] = $value;

        // not limited to modifying the one attribute
        $this->attributes['excerpt'] = substr($value,0,97) . '...';

    }

}
		
git checkout v5.2 app/models/Post.php app/routes.php /make-post SQL

Relationships

One-to-One

class Comment extends Eloquent {

  // comments table has an `author_id` field

  public function author()
  {
    return $this->hasOne('Author');
  }

}
$author = Comment::find(1)->author;
		

Inverse

class Author extends Eloquent {

  // authors table needs a `post_id` column

  public function comment()
  {
    return $this->belongsTo('Comment');
  }

}
$title = Author::find(1)->comment->title;
		

One-to-Many

class Book extends Eloquent {

  // books table has `author_id` column

  public function author()
  {
    return $this->belongsTo('Author');
  }

}

class Author extends Eloquent {

  public function books()
  {
    return $this->hasMany('Book');
  }

}
$my_books = Author::where('firstname','=','Colin')->books;
	

Many-to-Many

class Book extends Eloquent {
  public function tags()
  {
    return $this->belongsToMany('Tag');
  }
}

class Tag extends Eloquent {
  public function books()
  {
    return $this->belongsToMany('Book');
  }
}
		

Pivot Table

Schema::create('book_tag', function(Blueprint $table) {
  $table->increments('id');
  $table->integer('book_id')->unsigned()->index();
  $table->integer('tag_id')->unsigned()->index();
});
		
git checkout v6.0 app/model/Book.php app/model/Author.php app/model/Tags.php php artisan migrate php artisan db:seed SQL

Eager Loading

Book::with('author')->get();
	
Book::with('author','tags')->get();
	
Tag::with('books.author')->get();
	

Form Handing

Building Forms

{{ Form::open() }}

{{ Form::label('title') }}
{{ Form::text('title', 'default', array('class'=>'form-control') }}

{{ Form::label('text', 'Enter the full text:') }}
{{ Form::textarea('text', 'default', array('placeholder'=>'Enter the text') }}

{{ Form::email('email') }}

{{ Form::checkbox('is_active', 1, null, array('tabindex'=>4) }}
  {{ Form::label('is_active', 'Yes') }}

{{ Form::submit('Send it!')}}

{{ Form::close() }}
	

Handling Input

$input = Input::get('field','default');
	
$input = Input::only('firstname','lastname');
$input = Input::except('credit_card');
$input = Input::get('choices.0.name');
	
$input = Input::all();
	
$input = Input::file('upload_field');
	

Form Model Binding

$book = Book::findOrFail($id);
return View::make('books.edit', compact('book'));
	
{{ Form::model($book, array('route'=>array('books.update', $book->id), 'method'=>'PUT') ) }}

{{ Form::text('title')}}
{{ Form::textarea('description')}}
…

{{ Form::close() }}
	

Validation

Validation Rules

$data = array(
  'title' => 'My First Post',
  'text'  => 'Lorem hipster YOLO sic amet, semiotics banh mi flexitarian.',
));

$rules = array(
  'title'        => 'required|min:5',
  'description'  => 'required|max:100'
);
	

Validation

$validator = Validator::make( $data, $rules );
	
if ( $validator->fails() )       // ->passes()
{
  $messages = $validator->messages();
  …
}
	
if ( $messages->has('email') )
{
  echo $messages->first('email', '<p>:message</p>');
}
	
$messages->get('field');
$messages->all();
	

Validation Rules

$rules = array(
  'firstname'  => 'required',
  'lastname'   => 'alpha',
  'email'      => 'email',
  'age'        => 'integer|between:18,65',
  'agree'      => array('required','accepted'),
  'website'    => 'url',
  'password'   => 'confirmed',              // implies matching 'password_confirmation' data
  'company_id' => 'exists:companies,id',
  'gender'     => 'in:male,female',
  'photo'      => 'mimes:jpeg,png|max:100', // kilobytes
  'postalcode' => 'regex:^[A-Z][0-9][A-Z] ?[0-9][A-Z][0-9]$'
);
	

Logging

Logging

Built on top of Monolog

Log::info('This is some useful information.');

Log::info('Information with context', array('context' => 'Other helpful information'));

Log::warning('Something could be going wrong.');

Log::error('OMGWTFBBQ!');
	

app/storage/logs/laravel.log

Query Logging

$queries = DB::getQueryLog();
	
DB::connection()->disableQueryLog();
	
git checkout v6.1 /books /books/1

Caching

Cache Configuration

  • app/config/cache.php
  • Drivers:
    • file
    • database
    • apc
    • memcached
    • redis
    • array

Cache Usage

Cache::put('key', 'value', $minutes);
	
$expiresAt = Carbon::now()->addHours(10);
Cache::put('key', 'value', $expiresAt);
	
if (Cache::has('key')) { … }
	
$value = Cache::get('key');
$value = Cache::get('key', 'default');
$value = Cache::get('key', function() { return 'default'; });
	
$value = Cache::forever('key', 'value');
	
Cache::forget('key');
	

Advanced Cache Usage

$books = Cache::remember('all-books', 10, function()
{
  return Book::with('author')
    ->orderBy('title')
    ->get();
});
	
app/controllers/BookController.php /books etc.

Mail

Mail Configuration

  • Built on top of SwiftMailer
  • app/config/mail.php
  • Drivers:
    • smtp
    • mail
    • sendmail

Mail Usage

Mail::send('emails.view', $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!');
});
	
Mail::send( array('emails.view.html', 'emails.view.text'), $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!');
});
	
Mail::send( array('emails.view.html', 'emails.view.text'), $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!')
    ->cc('bar@example.com')
    ->bcc('snoop@nsa.gov');
});
	
Mail::send( array('emails.view.html', 'emails.view.text'), $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!')
    ->cc('bar@example.com')
    ->bcc('snoop@nsa.gov');

  $message->attach($pathToFile);
});
	
Mail::send( array('emails.view.html', 'emails.view.text'), $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!')
    ->cc('bar@example.com')
    ->bcc('snoop@nsa.gov');

  $message->attach($pathToFile, array('as' => $display, 'mime' => $mime));
});
	

Inline data

<body>
  Here is an image <img src="{{ $message->embed($pathToFile) }}">
</body>
	
<body>
  Here is raw image data <img src="{{ $message->embedData($data, $name) }}">
</body>
	

Queueing Mail

Mail::queue('emails.view', $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!');
});
	
Mail::later($seconds, 'emails.view', $data, function($message)
{
  $message->to('colin@example.com', 'Colin Viebrock')
    ->subject('Welcome!');
});
	

Queues

Queue Configuration

Queue Usage

$data = array('foo' => 'bar');

Queue::push('MyJob', $data);
	
class MyJob {

  public function fire($job, $data)
  {
    …
  }

}
	

Advanced Queue Usage

Queue::push('MyJob@someMethod', $data);
	
$date = Carbon::now()->addMinutes(15);
Queue::later($date, 'MyJob', $data);
	

Job Handling

public function fire($job, $data)
{
    // Process the job...

    $job->delete();
}
	
public function fire($job, $data)
{
    // Process the job...

    $job->release();
}
	
$job->release( $seconds );
	
if ( $job->attempts() > 3 )
{
  …
}
	

Queue Listening

php artisan queue:listen
	
php artisan queue:listen --sleep=5
	
php artisan queue:listen --timeout=60
	
php artisan queue:work
	

Authentication

Events

Localization

Packages

Further Reading

That’s All Folks!