Slide 1

Slide 1 text

Building Your First MVP in Laravel @cmgmyr

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Your takeaways • Better understanding of an MVP • See how Laravel can help you create an MVP faster

Slide 4

Slide 4 text

What is an MVP? A minimum viable product has just those core features that allow the product to be deployed, and no more. — wikipedia

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

What are we building?

Slide 7

Slide 7 text

SlickVoice.io Invoicing and payment service built with Lavavel and Stripe.

Slide 8

Slide 8 text

What do we NEED? (think MVP) • Authentication • Manage clients • Generate invoices • Process invoices on due dates, retry on errors

Slide 9

Slide 9 text

How much time would this MVP take? ...from start to deploy

Slide 10

Slide 10 text

Our Tools • Laravel • Elixir (Gulp) • Bootstrap & jQuery • Homestead (local development) • Forge (server provisioning) • Envoyer (zero downtime deployments)

Slide 11

Slide 11 text

Why Laravel? • Modern PHP Framework • Utilizes Composer • Aids in RAD (Rapid Application Development) • Simple to read API • Community & Resources • Easy path from development to deployment

Slide 12

Slide 12 text

Community & Resources • laravel.com (docs & API explorer) • laracasts.com (video tutorials, forums) • laravel.io (forums) • laravel-news.com (articles, packages) • larachat.co (Slack chat) • Laracon (Conference) US & UK • ...and so many other books, blogs, videos, sites

Slide 13

Slide 13 text

Key Laravel Features • Authentication • Eloquent ORM & Model Relationships • Blade Templating Engine • Events, Listeners, Jobs, and Queues • Task Scheduling • ...just to name a few

Slide 14

Slide 14 text

Development -> Deployment

Slide 15

Slide 15 text

Develop! With Homestead

Slide 16

Slide 16 text

Homestead Laravel Homestead is an official, pre-packaged Vagrant box that provides you a wonderful development environment -- https://laravel.com/docs/5.2/homestead

Slide 17

Slide 17 text

Homestead Included Software • Ubuntu 14.04 • Git • PHP 7.0 • HHVM • Nginx • MySQL, Sqlite3, Postgres • Composer • Node (With PM2, Bower, Grunt, and Gulp) • Redis • Memcached • Beanstalkd

Slide 18

Slide 18 text

Provision! With Forge

Slide 19

Slide 19 text

Key Features: • Installs: Nginx, PHP 7.0, MySQL, Postgres, Redis, Memcached, etc • Push to deploy* • Load balancers • Queues & Crons • SSL Security

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Deploy! With Envoyer

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Homestead -> Forge -> Envoyer

Slide 24

Slide 24 text

Install Laravel (in Homestead) composer create-project --prefer-dist laravel/laravel slickvoice_mvp

Slide 25

Slide 25 text

Laravel's File Structure app/ config/ database/ public/ resources/ .env artisan composer.json gulpfile.js

Slide 26

Slide 26 text

Artisan CLI Artisan is the name of the command-line interface included with Laravel. It provides a number of helpful commands for your use while developing your application. It is driven by the powerful Symfony Console component. To view a list of all available Artisan commands, you may use the list command: php artisan list -- https://laravel.com/docs/5.2/artisan

Slide 27

Slide 27 text

Artisan Highlights • make:[controller|event|job|migration|model|seeder|...] • migrate[:refresh] • queue:listen • schedule:run • ...and custom commands! • sv:process-invoices

Slide 28

Slide 28 text

Easy win for Laravel/Artisan

Slide 29

Slide 29 text

Authentication Scaffolding $ php artisan make:auth Created View: /slickvoice_mvp/resources/views/auth/login.blade.php Created View: /slickvoice_mvp/resources/views/auth/register.blade.php Created View: /slickvoice_mvp/resources/views/auth/passwords/email.blade.php Created View: /slickvoice_mvp/resources/views/auth/passwords/reset.blade.php Created View: /slickvoice_mvp/resources/views/auth/emails/password.blade.php Created View: /slickvoice_mvp/resources/views/layouts/app.blade.php Created View: /slickvoice_mvp/resources/views/home.blade.php Created View: /slickvoice_mvp/resources/views/welcome.blade.php Installed HomeController. Updated Routes File. Authentication scaffolding generated successfully!

Slide 30

Slide 30 text

What do we have so far?

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Thanks Laravel! ...but let's make it better

Slide 36

Slide 36 text

Laravel Elixir (Gulp) var elixir = require('laravel-elixir'); var paths = { 'bootstrap': 'vendor/bower_components/bootstrap/', 'jquery': 'vendor/bower_components/jquery/dist/', 'fontawesome': 'vendor/bower_components/fontawesome/' }; elixir(function(mix) { mix.sass('app.scss') .scripts([ paths.jquery + 'jquery.js', paths.bootstrap + 'dist/js/bootstrap.js', 'resources/assets/js/app.js' ], 'public/js/app.js', './') .copy(paths.bootstrap + 'fonts/**', 'public/build/fonts') .copy(paths.fontawesome + 'fonts/**', 'public/build/fonts') .version(['css/app.css', 'js/app.js']); });

Slide 37

Slide 37 text

Blade resources/views/layouts/app.blade.php SlickVoice
@yield('content')

Slide 38

Slide 38 text

A little design facelift...

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Migrations & Seeders

Slide 42

Slide 42 text

Create Migrations $ php artisan make:migration CreateClientsTable Schema::create('clients', function (Blueprint $table) { $table->increments('id')->unsigned(); $table->string('stripe_id')->unique(); $table->string('email')->unique(); $table->string('name')->nullable(); $table->integer('card_last_four'); $table->integer('card_exp_month'); $table->integer('card_exp_year'); $table->string('card_brand'); // ... address & phone info $table->timestamps(); });

Slide 43

Slide 43 text

Populate tables with initial data class DatabaseSeeder extends Seeder { public function run() { $this->call(ClientsTableSeeder::class); // $this->call(InvoicesTableSeeder::class); // $this->call(InvoiceItemsTableSeeder::class); } } class ClientsTableSeeder extends Seeder { public function run() { Client::create([ 'stripe_id' => 'cust_123', 'name' => 'Chris Gmyr', 'email' => '[email protected]', // ... ]); } }

Slide 44

Slide 44 text

Models & Eloquent

Slide 45

Slide 45 text

Create a Model $ php artisan make:model Client

Slide 46

Slide 46 text

app/Client.php use Illuminate\Database\Eloquent\Model; class Client extends Model { protected $table = 'clients'; // optional, but I like it protected $fillable = [ 'stripe_id', 'email', 'name', 'card_last_four', // list other columns here... ]; public function invoices() { return $this->hasMany(Invoice::class); } }

Slide 47

Slide 47 text

Eloquent is...Eloquent // get all clients $clients = Client::all(); // Find client by id $client = Client::find(1); // Find client by stripe_id $client = Client::where('stripe_id', 'cus_123')->first(); // Dynamic methods $client = Client::whereStripeId('cust_123')->first(); // Create a client $client = Client::create([ 'stripe_id' => 'cus_123', 'name' => 'Test Client', 'email' => '[email protected]', // ... ]); // Update a client $client->email = '[email protected]'; $client->save();

Slide 48

Slide 48 text

Relationships are easier with Eloquent // All invoices for a client $clientInvoices = $client->invoices->all(); // Get pending invoices for a client $pending = $client->invoices()->where('status', 'pending')->get(); // Add invoice to a client $invoice = Invoice::create(['data' => 'here']); $client->invoices()->save($invoice); // Add invoice items to an invoice $items[] = InvoiceItem::create(['data' => 'here']); $items[] = InvoiceItem::create(['data' => 'here']); $invoice->items()->saveMany($items);

Slide 49

Slide 49 text

Controllers & Routes

Slide 50

Slide 50 text

Create a Controller $ php artisan make:controller ClientsController --resource

Slide 51

Slide 51 text

namespace Sv\Http\Controllers; class ClientsController extends Controller { // Display a listing of the resource. public function index(){} // Show the form for creating a new resource. public function create(){} // Store a newly created resource in storage. public function store(Request $request){} // Display the specified resource. public function show($id){} // Show the form for editing the specified resource. public function edit($id){} // Update the specified resource in storage. public function update(Request $request, $id){} // Remove the specified resource from storage. public function destroy($id){} }

Slide 52

Slide 52 text

Build Routes Route::group(['middleware' => 'auth'], function () { Route::group(['prefix' => 'clients'], function () { Route::get('/', ['as' => 'clients.index', 'uses' => 'ClientsController@index']); Route::get('create', ['as' => 'clients.create', 'uses' => 'ClientsController@create']); Route::post('/', ['as' => 'clients.store', 'uses' => 'ClientsController@store']); Route::get('{id}/edit', ['as' => 'clients.edit', 'uses' => 'ClientsController@edit']); Route::put('{id}', ['as' => 'clients.update', 'uses' => 'ClientsController@update']); Route::delete('{id}', ['as' => 'clients.destroy', 'uses' => 'ClientsController@destroy']); }); });

Slide 53

Slide 53 text

Putting it together

Slide 54

Slide 54 text

Client Index public function index() { $clients = Client::orderBy('name', 'ASC')->simplePaginate(15); return view('clients.index', compact('clients')); }

Slide 55

Slide 55 text

resources/views/clients/index.blade.php @section('content') Stripe ID Name Email Manage @foreach($clients as $client) {{ $client->stripe_id }} {{ $client->name }} {{ $client->email }} Edit @endforeach @stop @section('pagination')
{!! $clients->render() !!}
@stop

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

Fast Forward! • Build out Clients CRUD • Build out Invoices & Items CRUD

Slide 58

Slide 58 text

Processing Invoices Artisan Commands & Task Scheduling

Slide 59

Slide 59 text

app/Console/Kernel.php class Kernel extends ConsoleKernel { protected $commands = [ ProcessInvoices::class, ]; protected function schedule(Schedule $schedule) { $schedule->command('sv:process-invoices')->hourly(); } }

Slide 60

Slide 60 text

app/Console/Commands/ProcessInvoices.php class ProcessInvoices extends Command { use DispatchesJobs; protected $signature = 'sv:process-invoices'; protected $description = 'Processes pending invoices for the day'; public function handle() { $invoices = Invoice::whereIn('status', ['pending', 'overdue']) ->where('num_tries', '<=', 3) ->whereDate('try_on_date', '<=', Carbon::today()->toDateString()) ->get(); if ($invoices->count() > 0) { foreach ($invoices as $invoice) { $this->dispatch(new PayInvoice($invoice)); } $this->info('Invoices Processed!'); } } }

Slide 61

Slide 61 text

app/Jobs/PayInvoice.php class PayInvoice extends Job implements ShouldQueue { public function __construct(Invoice $invoice) { $this->invoice = $invoice; } public function handle() { $stripe_id = $this->invoice->client->stripe_id; $total = $this->invoice->items->sum('price') * 100; // stripe uses cents instead of dollars try { // ... } catch (Exception $e) { $this->tryAgain(); } } }

Slide 62

Slide 62 text

try { $charge = StripeCharge::create([ 'amount' => $total, 'currency' => 'usd', 'customer' => $stripe_id, ]); // change status, add charge date, charge id, etc...save event(new InvoiceWasPaid($this->invoice)); if ($this->invoice->repeat != 'no') { $this->dispatch(new DuplicateInvoice($this->invoice, $this->invoice->repeat)); } }

Slide 63

Slide 63 text

protected function tryAgain() { $this->invoice->status = 'overdue'; if ($this->invoice->num_tries >= 3) { $this->invoice->status = 'error'; } $this->invoice->try_on_date = Carbon::tomorrow(); $this->invoice->increment('num_tries'); $this->invoice->save(); event(new InvoiceWasNotPaid($this->invoice)); }

Slide 64

Slide 64 text

Events & Listeners

Slide 65

Slide 65 text

app/Providers/EventServiceProvider.php class EventServiceProvider extends ServiceProvider { protected $listen = [ 'Sv\Events\InvoiceWasPaid' => [ 'Sv\Listeners\SendClientPaidInvoice', 'Sv\Listeners\SendAdminsPaidInvoice', ], 'Sv\Events\InvoiceWasNotPaid' => [ 'Sv\Listeners\SendClientNotPaidInvoice', 'Sv\Listeners\SendAdminsNotPaidInvoice', ], ]; }

Slide 66

Slide 66 text

app/Events/InvoiceWasPaid class InvoiceWasPaid extends Event { public $invoice; public function __construct(Invoice $invoice) { $this->invoice = $invoice; } }

Slide 67

Slide 67 text

app/Listeners/SendClientPaidInvoice.php class SendClientPaidInvoice implements ShouldQueue { public function handle(InvoiceWasPaid $event) { $invoice = $event->invoice; Mail::queue('emails.invoice', compact('invoice'), function ($m) use ($invoice) { $m->to($invoice->client->email, $invoice->client->name); $m->subject('Your Payment Receipt!'); }); } }

Slide 68

Slide 68 text

Where are we now? • Authentication • CRUD Clients & Invoices • Invoice Processing • Email Notifications

Slide 69

Slide 69 text

All of Our Needs With a Few Extras!

Slide 70

Slide 70 text

How much time would this have taken in another language, framework, and/or platform? • Building similar components found in Laravel? • Local development setup? • Provisioning a server? • (Making sure these environments match?) • Deployments that don't take down your site?

Slide 71

Slide 71 text

Time to Complete this MVP?

Slide 72

Slide 72 text

~18 Hours

Slide 73

Slide 73 text

LIVE DEMO!!!

Slide 74

Slide 74 text

Thank you! Feedback: Joind.in: https://joind.in/talk/ab6ad (or search TrianglePHP) Download: Source: github.com/cmgmyr/mvp.slickvoice.io Slides: speakerdeck.com/cmgmyr/building-your-first-mvp-in-laravel Connect: cmgmyr [gmail | twitter | github | .com]