Why we use Laravel

Or, why we never want to write another database access class

Who are we?

Ciaran Downey

Justin Page

Today we're going to talk about...

Let's get started

But first, a bit of history

Laravel 4: A PHP Framework for Web Artisans

Okay, enough history
what about the features?!

SQL and database access

The Laravel way

// connections
$connections = [
    "mysql" => [
        "driver"    => "mysql",
        "host"      => "localhost",
        "database"  => $dbname,
        "username"  => $dbuser,
        "password"  => $dbpass,
        "charset"   => "utf8",
        "collation" => "utf8_unicode_ci",
        "prefix"    => "",
    ],
];

The Laravel way

$query = DB::table("developers")
    ->select("*")
    ->where("developers.age", ">", 30)
    ->whereNull("retired_at")
    ->orderBy("salary", "DESC")
    ->get();

The vanilla PHP way

$dsn = "{$dbtype}:dbname={$dbname};host={$host}";

$conn = new PDO($dsn, $dbuser, $dbpass);

//TODO: set charset, collation, table prefixes

The vanilla PHP way

$query = <<<QUERY
    SELECT *
    FROM developers
    WHERE age > :age
    AND WHERE retired_at IS NULL
    ORDER BY salary DESC
QUERY;

$prep = $conn->prepare($query);

$prep->execute([":age" => 30]);

do_something_with_these_errors($prep->errorInfo());

Problems with the vanilla way

SQL joins

The Laravel way

DB::table('developers')
    // select these columns
    ->select('developers.first_name', 'projects.project_name')
    // LEFT JOIN projects ON developers.id = projects.developer_id
    ->join('projects', 'developers.id', '=', 'projects.developer_id')
    // add any number of conditions
    ->where('projects.due_date', '<', $due_date)
    ->whereNull('projects.completed_at')
    // arbritrary clauses
    ->orderBy('projects.due_date', 'ASC')
    // limit by X
    ->take($limit)
    // perform the query
    ->get();

The vanilla PHP way

$query = <<<QUERY
    SELECT first_name, project_name
    FROM developers
    INNER JOIN projects
        ON developers.id = developer_id
    WHERE due_date < ':due_date'
    ORDER BY due_date ASC LIMIT :limit
QUERY;

$prep = $conn->prepare($query);
$prep->execute(
    [":due_date" => $due_date, ":limit" => $limit]
);

do_something_with_these_errors($prep->errorInfo());

Retrieving records from a database

The Laravel way

class User extends Eloquent
{
    // don't need to add anything here
}

The Laravel way (cont'd)

// grabbing all the users:
$all_users = User::all();

// grabbing the user with pk = 3
$user_no_3 = User::find(3);

The Laravel way (cont'd)

// grabbing the first 10 teenage users
// organized by age (old -> young)
$teenagers = User::whereBetween("age", [13, 19])
    ->orderBy("age", "desc")
    ->take(10)
    ->get();

The Laravel way (cont'd)

$user = new User();

$user->first_name = "Ciaran";
$user->last_name  = "Downey";
$user->profession = "Developer";

try {
    $primary_key = $user->save()
} catch (Exception $e) {
    Log::error($e->getMessage());

    throw $e;
}

The vanilla PHP way

class BaseRepo
{
    private $conn;

    public function __construct(PDO $conn) {
        $this->conn = $conn;
    }

    // continued

The vanilla PHP way

    public function fetchByQuery($query)
    {
        $stmt = $this->conn->prepare($query);

        $stmt->execute();

        return $stmt->fetch();
    }
}

The vanilla PHP way (cont'd)

class UserRepo extends BaseRepo {

    public function findByPk($key) {
        $safe_key = intval($key);

        return $this->fetchByQuery(
            "SELECT * FROM users
              WHERE id = {$safe_key}"
        );
    }

The vanilla PHP way (cont'd)

    // continued
    public function findAll()
    {
        return $this->fetchByQuery(
            "SELECT * FROM users"
        );
    }
}

The vanilla PHP way (cont'd)

$repo = new UserRepo($db_from_somewhere);

// grab all the users
$all_users = $repo->findAll();

// grabbing the user with pk = 3
$user_no_3 = $repo->findByPk(3);

The vanilla PHP way (cont'd)

// grabbing the first 10 teenage users
// organized by age (old -> young)
$teenagers = $repo->fetchByQuery(
    "SELECT * FROM users
        WHERE age BETWEEN 13 AND 19
        ORDER BY age DESC LIMIT 10"
);

The vanilla PHP way (cont'd)

$user = [
    "first_name" => "Justin",
    "last_name"  => "Page",
    "profession" => "Developer",
];

$query = <<<QUERY
    INSERT INTO users
        (first_name, last_name, profession)
    VALUES
        (':first', ':last', ':profession')
QUERY;

The vanilla PHP way (cont'd)

/*

now we just need to add:

 - validation
 - param binding
 - nice error messages
 - data transformation before / after save

*/

nope

Other benefits Laravel provides

Autoloading

The Laravel way

Use composer

The Composer way

{
    "require": {
        "laravel/framework": "~4.2.3"
    },
    "autoload": {
        "psr-4": {"MyNamespace\\": "src/"}
    },
    "config": { "preferred-install": "dist" },
    "minimum-stability": "stable"
}

The Composer way (cont'd)

<?php // entry file

require __DIR__ . "/vendor/autoload.php";

The Composer way (cont'd)

// as long as the autoload file has been included
// before, we don't have to specify anything else

$cat = new MyNamespace\Model\Cat();

// loaded only when used = lazy loading
$hat = new MyNamespace\Fashion\Hat();

$cat->moveInside($hat);

The Old way

// ever seen something like this?
require "../inc/libs/_globals/bootstrap_NEW.php";

// or this?
foreach ($required_files as $file) {
    require $file;
}

// or this?
foreach (glob("../lib/*.php") as $file) {
    require $file;
}

PUBLIC SERVICE ANNOUNCEMENT

USE COMPOSER

Basic routing

The Laravel way

Route::get('/blog', function() {
    // do blog stuff
});

// or

Route::get("/blog", "BlogController@showBlog");

The Laravel way (cont'd)

// different request methods

Route::get("/blog/post/{id}", function ($id) {
    // show that post
});

Route::delete("/blog/post/{id}", function ($id) {
    // delete that post
});

The vanilla PHP way

+-- about
   |   +-- index.php
   +-- blog
       +-- index.php
       +-- post
           +-- index.php

The vanilla PHP way

switch (strtoupper($_SERVER["REQUEST_METHOD")) {
    "GET":
        // show the post
        break;
    "DELETE":
        // do delete stuff
        break;
    default:
        // show a 404
}

CSRF tokens

CSRF is an attack which forces an end user to execute unwanted actions on a web application in which he/she is currently authenticated

owasp.org

Does everyone know what CSRF is?

And you're all protecting yourselves correctly, right?

The Laravel way

<input type="hidden"
    name="_token"
    value="<?php echo csrf_token(); ?>">

The Laravel way (cont'd)

Route::post('register', [
    // filters to apply
    'before' => 'csrf',
    function() {
        return 'You gave a valid CSRF token!';
    }
]);

// Or
Route::post('register',
    ["before" => "csrf", "Controller@register"]
);

The vanilla PHP way

// generates the csrf token
function csrf_token()
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    // lolwat
    $_SESSION["token"] =
        md5(uniqid(mt_rand(), true)));

    return $_SESSION["token"];
}

The vanilla PHP way (cont'd)

<input type="hidden"
    name="_token"
    value="<?php echo csrf_token(); ?>">

The vanilla PHP way (cont'd)

function check_token()
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    // works with both POST and GET
    $req = count($_POST) ? $_POST : $_GET;

    return isset($req["_token"])
        && $req["_token"] !== $_SESSION["token"];
}

The vanilla PHP way (cont'd)

// now just call that in every file you need it
if (check_token()) {
    show_error_page_or_whatever_we_do();
}

The vanilla PHP way (cont'd)

// except...

// remember this line?
$_SESSION["token"] =
    md5(uniqid(mt_rand(), true)));

// are you sure that's secure?

The vanilla PHP way (cont'd)

// Here's what Laravel does
$random = 'openssl_random_pseudo_bytes';
if (function_exists($random)) {
    $bytes  = $random($length * 2);
    $base64 = base64_encode($bytes);

    return substr(
        str_replace(['/', '+', '='], '', $base64),
        0,
        $length
    );
}

The vanilla PHP way (cont'd)

// or if we don't have openssl_random_pseudo_bytes
} else {
    $pool = '0123456789'
        . 'abcdefghipqrstuvwxyz'
        . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

    return substr(
        str_shuffle(str_repeat($pool, 5)),
        0,
        $length
    );
}

Questions for the audience

Anyone, from the most clueless amateur to the best cryptographer, can create an algorithm that he himself can't break.

Bruce Schneier

Comparing dates

The Carbon way

// Carbon comes included with Laravel
use Carbon\Carbon;

Carbon::now()->diffForHumans(
    Carbon::parse("July 4th, 2014")
);
// => "1 week before"

// other cool things
Carbon::yesterday();
Carbon::now()->isWeekend();
Carbon::now()->subWeekdays(5);

The vanilla PHP way

die("nope.gif");

nope.gif

Templating

The Laravel way

<html lang='en'><!-- app/views/master.php -->
<head>
    <title><?= Section::yield("title") ?></title>
</head>
<body>
    <div class="container">
        <?= Section::yield("content") ?>
    </div>
    <?php if ($projects): ?>
        <?= Section::yield("js") ?>
    <?php endif; ?>
</body>
</html>

The Laravel way (cont'd)

<?php include __DIR__ . "/../master.php";
// include the master template

// start a new section called "content"
Section::startSection("content"); ?>

Nothing magic here! Everything gets rendered in
the master template above. You get access to all
the things you'd normally have access to. See?

The year is: <?= date("Y") ?>

<?php // and just stop the section when you're done
Section::stopSection("content");

The vanilla PHP way

<html lang='en'><!-- app/views/master.php -->
<head>
    <title><?= $title ?></title>
</head>
<body>
    <div class="container">
        <?= $content ?>
    </div>
    <?php if ($projects): ?>
        <?= $js ?>
    <?php endif; ?>
</body>
</html>

The vanilla PHP way (cont'd)

<?php /* start output buffering */ ob_start(); ?>
Nothing magic here! Everything gets rendered in
the master template above. You get access to all
the things you'd normally have access to. See?

The year is: <?= date("Y") ?>

<?php

$content  = ob_get_clean();
$title    = "My Cool Title";
$projects = [];

include __DIR__ . "/../master.php";

Problems with the vanilla way

Other benefits Laravel provides

Migrations

Getting started

What goes up…

public function up()
{
    // create a "projects" table and apply these features
    Schema::create('projects', function(Blueprint $table) {
        // primary key named 'id'
        $table->increments('id');
        // a unique string named 'project_name'
        $table->string('project_name')->unique();
        $table->string('developer_id');

        $table->dateTime('due_date');
        $table->dateTime('completed_at')->nullable();
        // "created_at" and "updated_at" columns
        $table->timestamps();
    });
}

…must come down

public function down()
{
    // drop the table
    Schema::drop('projects');
}

More options

Benefits of Migrations

Simple CRUD using REST

Why REST?

Register a route resource

Route::resource('developers', 'DevelopersController');
Verb Path Action Route Name
GET /resource index resource.index
GET /resource/create create resource.create
POST /resource store resource.store
GET /resource/{resource} show resource.show
GET /resource/{resource}/edit edit resource.edit
PUT/PATCH /resource/{resource} update resource.update
DELETE /resource/{resource} destroy resource.destroy

So what does that mean?

class DevelopersController
{
    // show an index page for this resource
    public function index();
    // show a create form for this resource
    public function create();
    // store a resource
    public function store();
    // show the requested resource
    public function show($id);
    // show an edit form for the requested resource
    public function edit($id);
    // update the requested resource
    public function update($id);
    // delete the requested resource
    public function destroy($id);
}

Display a listing of the developers

public function index()
{
    $devs = Developer::all();

    return View::make('developers.index', ["devs" => $devs]);
}

Show form for creating a new developer

public function index()
{
    $devs = Developer::all();

    return View::make('developers.index', ["devs" => $devs]);
}

Store a newly created developer in storage

public function store()
{
    $dev = new Developer;

    $dev->first_name = Input::only("first_name");
    $dev->last_name  = Input::only("last_name");

    $dev->save();

    return Redirect::route('developers.index');
}

You may want to take advantage of mass assignment

public function store()
{
    Developer::create(Input::all());

    return redirect::route('developers.index');
}

Guard against mass assignment vulnerability with a whitelist

class Developer extends Eloquent
{
    protected $fillable = ['first_name', 'last_name'];
}
Make you still do input validation!

Display the specified developer

public function show($id)
{
    // shows a 404 if it fails
    $dev = Developer::findOrFail($id);

    return View::make('developers.show', ['dev' => $dev]);
}

Show the form for editing a developer

public function edit($id)
{
    // shows a 404 if it fails
    $dev = Developer::findOrFail($id);

    return View::make('developers.edit', ["dev" => $dev]);
}

Use form-model binding

<?= Form::model($dev,
    ["method" => "PATCH", "route" => "developers.update", $dev->id]
) ?>
    <div>
        <?= Form::label('first_name', 'First Name:') ?>
        <?= Form::text('first_name') ?>
    </div>
    <div>
        <?= Form::label('last_name', 'Last Name:') ?>
        <?= Form::text('last_name') ?>
    </div>
    <div><?= Form::submit('Update Developer') ?></div>
<?= Form::close() ?>

Update the specified resource in storage

public function update($id)
{
    $dev = Developer::findOrFail($id);

    $dev->fill(Input::all());

    $dev->save();

    return Redirect::route('developers.show', ['id' => $id]);
}

Remove specified resource from storage

public function destroy($id)
{
    // grab the model first
    Developer::findOrFail($id)->delete();
    // OR
    $affectedRows = Developer::destroy($id);

    return Redirect::route('developers.index');
}

Different ways to redirect

// Redirect to a route
return Redirect::to('developer/login');

// Redirect to a named route
return Redirect::to('login');

// Redirect to a Controller action
return Redirect::action('DevelopersController@index');

Authentication

Authentication and Laravel

Database Seeder

class DeveloperTableSeeder extends Seeder
{
    public function run()
    {
        Developer::truncate();

        Developer::create([
            'email'      => 'ciaran@ciarandowney.com',
            'username'   => 'ciarand',
            'password'   => Hash::make('1234'),
        ]);
    }
}

Register a Login and Logout

Route::get('login', 'AuthController@login');
Route::post('login', 'AuthController@processLogin');
Route::get('logout', 'AuthController@logout');

// we don't want just anyone to access this
Route::get('projects', function() {
    return 'List of Super Secret Projects';
    // so we run the auth filter before showing it to anyone
})->before('auth');

Authentication Filter

// the auth filter
Route::filter('auth', function() {
    if (Auth::guest()) {
        // sneaky sneaky
        return Redirect::guest('login');
    }
});

Login a user

public function login()
{
    if (Auth::check()) {
        // if they're already logged in they shouldn't be here
        return Redirect::to('/projects');
    }

    return View::make('developers.login');
}
public function processLogin()
{
    if (Auth::attempt(Input::only('email', 'password'))) {
        return Redirect::to('/projects')
            ->with('message', 'Successfully logged in');
    }

    return Redirect::back()
        ->with('message', 'Invalid credentials')
        ->withInput();
}

Logout the user

public function logout()
{
    Auth::logout();

    return Redirect::home()->with('message', 'Logged out');
}

Authentication Facade

The IoC Container and Dependency Injection

Inversion of Control

Dependency Injection" is a 25-dollar term for a 5-cent concept. [...] Dependency injection means giving an object its instance variables. [...].

Martin Fowler

Problem

class DevelopersController extends BaseController
{
    public function index()
    {
        $devs = Developer::all();

        return View::make('developers.index', ["devs" => $devs]);
    }
}

Inversion of Control Container

interface DeveloperRepositoryInterface
{
    public function all();
}

class DBDeveloperRepository implements DeveloperRepositoryInterface
{
    protected function all()
    {
        return Developer::all();
    }
}

Dependency Injection

class DevelopersController extends BaseController
{
    protected $repo;

    public function __construct(DeveloperRepositoryInterface $repo)
    {
        $this->repo = $repo;
    }

    public function index()
    {
        $devs = $this->repo->all();

        return View::make('developers.index', ["devs" => $devs]);
    }
}

Binding our Dependencies

App::bind( // bind this interface
    'DeveloperRepositoryInterface',
    'DBDeveloperRepository' // to this implementation
);

// Sometimes you may wish to resolve only one instance of a given
// class throughout your entire application.
App::singleton( // bind this interface
    'DeveloperRepositoryInterface',
    'DBDeveloperRepository' // to the same object
);

// Or you may wish to pass through an already existing instance
App::singleton('DeveloperRepositoryInterface', $repo);

Benefits

lost.gif

Interested in learning more?

Have some links!

The official documentation

Laracasts (semi-official)

Code Bright (by Dayle Rees)

Wait, you're going too fast!

Don't worry!

We're on GitHub!

Links

Thank you!

/