Slide 1

Slide 1 text

Code/Demo Server book-jacket-api book-info-website Book Jacket API Rating API Book Info Website Browser ratings-api {server}.herokuapp.com https://github.com/PurpleBooth/{server}

Slide 2

Slide 2 text

MVVM and Silex Welcome to the world of tomorrow!

Slide 3

Slide 3 text

Goals • Working knowledge • JavaScript serverside • Client side JavaScript frameworks • Know how to integrate them with PHP • Become more of a Cross Stack developer

Slide 4

Slide 4 text

Who’re you? Billie Thompson @PurpleBooth [email protected] PHP Contractor

Slide 5

Slide 5 text

Imagination Time! http://www.businesscat.happyjar.com/comic/coffee/

Slide 6

Slide 6 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 7

Slide 7 text

Book Information Service DB Queues Logic Responsive Extendible Clean Parallel Serve JS Frontend-y Book Jacket API Rating API Book Info Website Browser

Slide 8

Slide 8 text

Book Information Service Silex PHP AngularJS JavaScript Express JavaScript Book Jacket API Rating API Book Info Website Browser

Slide 9

Slide 9 text

Code/Demo Server book-jacket-api book-info-website Book Jacket API Rating API Book Info Website Browser ratings-api {server}.herokuapp.com https://github.com/PurpleBooth/{server}

Slide 10

Slide 10 text

Silex Solid & simple

Slide 11

Slide 11 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 12

Slide 12 text

–Silex Demo “Please don’t break”

Slide 13

Slide 13 text

// book-jacket-api/public/index.php register(new Silex\Provider\ServiceControllerServiceProvider());
 
 $app['books.controller'] = $app->share(function() use ($app) {
 return new BooksController();
 });
 
 $app->get('/hello/{name}', function($name) use($app) {
 return 'Hello '.$app->escape($name);
 });
 
 $app->get('/', "books.controller:indexAction");
 $app->get('/{isbn}', "books.controller:getAction");
 
 
 $app->run();

Slide 14

Slide 14 text

// book-jacket-api/public/index.php
 
 require_once __DIR__.'/../vendor/autoload.php';
 
 $app = new Silex\Application();
 …
 
 $app->run();

Slide 15

Slide 15 text

// book-jacket-api/public/index.php … $app->get('/hello/{name}', function($name) use($app) {
 return 'Hello '.$app->escape($name);
 });
 …

Slide 16

Slide 16 text

// book-jacket-api/public/index.php use PurpleBooth\Controller\BooksController; use Silex\Provider\ServiceControllerServiceProvider; … $app->register(new ServiceControllerServiceProvider());
 … 
 $app['books.controller'] = $app->share(function() use ($app) {
 return new BooksController();
 });
 
 $app->get('/', "books.controller:indexAction");
 $app->get('/{isbn}', "books.controller:getAction");

Slide 17

Slide 17 text

bookData));
 
 }
 
 public function getAction($isbn)
 {
 if (!array_key_exists($isbn, $this->bookData)) {
 return new JsonResponse(null, 404);
 }
 
 return new JsonResponse($this->bookData[$isbn]);
 }
 }

Slide 18

Slide 18 text

bookData)) {
 return new JsonResponse(null, 404);
 }
 
 return new JsonResponse($this->bookData[$isbn]);
 }
 }

Slide 19

Slide 19 text

Silex for the business logic • Rapid API Development • Familiar libraries (Symfony, etc) • Perfect for small services

Slide 20

Slide 20 text

Alternatives? • Apigility • ZF2 • Symfony

Slide 21

Slide 21 text

MVVM Model, View and View Model

Slide 22

Slide 22 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 23

Slide 23 text

MVVM Framework View View Model Model Remote API Browser Server

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

–AngularJS Demo “It looked so easy when I saw someone else’s talk”

Slide 26

Slide 26 text

//- book-info-website/views/index.jade doctype html
 html(lang="en" ng-app="booksApp")
 head
 title Book Info Website
 link(rel='stylesheet', href='/stylesheets/style.css')
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular-resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')
 script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')
 body
 div(ng-view)

Slide 27

Slide 27 text


 
 
 
 Book Info Website
 
 
 
 
 
 
 
 
 
 


 
 


Slide 28

Slide 28 text

//- book-info-website/views/index.jade 
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular-resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')

Slide 29

Slide 29 text

//- book-info-website/views/index.jade script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')

Slide 30

Slide 30 text

// book-info-website/public/javascripts/angular/app.js
 var booksApp = angular.module('booksApp', [
 'ngResource', 
 'ngRoute',
 'booksServices',
 'booksControllers'
 ]);
 
 booksApp.config(['$routeProvider',
 function($routeProvider) {
 $routeProvider.
 when('/', {
 templateUrl: 'partials/list.html',
 controller: 'IndexController'
 }).
 when('/:isbn', {
 templateUrl: 'partials/details.html',
 controller: 'DetailController'
 }).
 otherwise({
 redirectTo: '/'
 });
 }]);

Slide 31

Slide 31 text

//- book-info-website/views/index.jade html(lang="en" ng-app=“booksApp”) … // book-info-website/public/javascripts/angular/app.js 
 var booksApp = angular.module('booksApp', [
 'ngResource', 
 'ngRoute',
 'booksServices',
 'booksControllers'
 ]); 


Slide 32

Slide 32 text

// book-info-website/public/javascripts/angular/app.js booksApp.config(['$routeProvider',
 function($routeProvider) {
 $routeProvider.
 when('/', {
 templateUrl: 'partials/list.html',
 controller: 'IndexController'
 }).
 when('/:isbn', {
 templateUrl: 'partials/details.html',
 controller: 'DetailController'
 }).
 otherwise({
 redirectTo: '/'
 });
 }]); … //- book-info-website/views/index.jade body
 div(ng-view)

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

//- book-info-website/views/partials/list.jade
 div(ng-controller="IndexController")
 h1= title
 h2 Book list!
 ul
 li(ng-repeat="book in books")
 a(href="#/{{book.isbn}}") {{book.title}}

Slide 35

Slide 35 text

// book-info-website/public/javascripts/angular/controllers.js
 var booksControllers = angular.module('booksControllers', []);
 
 booksControllers.controller('IndexController', ['$scope', 'Book', function ($scope, Book) {
 Book.query({}, function (books) {
 $scope.books = books;
 });
 }]);
 
 booksControllers.controller(
 'DetailController',
 ['$scope', '$routeParams', 'Book',
 function ($scope, $routeParams, Book) {
 Book.pushGet({isbn: $routeParams.isbn}, function (book) {
 $scope.book = book;
 });
 
 $scope.$on('$destroy', function () {
 Book.stopPushGet();
 });
 }
 ]
 );

Slide 36

Slide 36 text

// book-info-website/public/javascripts/angular/controllers.js var booksControllers = angular.module('booksControllers', []);
 
 booksControllers.controller( 'IndexController', ['$scope', 'Book', function ($scope, Book) {
 $scope.books = Book.query();
 }] ); …

Slide 37

Slide 37 text

// book-info-website/public/javascripts/angular/services.js
 var booksService = angular.module('booksServices', []);
 
 booksService.factory('Book', ['$resource', '$timeout',
 function ($resource, $timeout) {
 var api = $resource('api/v2/:isbn', {'isbn': '@id'});
 
 var promise;
 var pushing = false;
 
 api.pushGet = function (params, callback) {
 if (!pushing) {
 pushing = true;
 
 var refreshData = function () {
 api.get(params, function (book) {
 callback(book);
 
 promise = $timeout(
 function () {
 refreshData(params, callback);
 }, 4000);
 });
 };
 
 refreshData(params, callback);
 }
 };
 
 api.stopPushGet = function () {
 pushing = false;
 
 if (angular.isDefined(promise)) {
 $timeout.cancel(promise);
 promise = undefined;
 }
 };
 
 return api;
 }]);

Slide 38

Slide 38 text

// book-info-website/public/javascripts/angular/services.js var booksService = angular.module('booksServices', []);
 
 booksService.factory('Book', ['$resource', '$timeout',
 function ($resource, $timeout) {
 var api = $resource('api/v2/:isbn', {'isbn': '@id'});
 …
 return api;
 }]);


Slide 39

Slide 39 text

MVVM Framework View View Model Model Remote API Browser Server

Slide 40

Slide 40 text

// book-info-website/public/javascripts/angular/controllers.js booksControllers.controller(
 'DetailController',
 ['$scope', '$routeParams', 'Book',
 function ($scope, $routeParams, Book) {
 Book.pushGet({isbn: $routeParams.isbn}, function (book) {
 $scope.book = book;
 });
 
 $scope.$on('$destroy', function () {
 Book.stopPushGet();
 });
 }
 ]
 );

Slide 41

Slide 41 text

// book-info-website/public/javascripts/angular/services.js var promise;
 var pushing = false;
 
 api.pushGet = function (params, callback) {
 if (!pushing) {
 pushing = true;
 
 var refreshData = function () {
 api.get(params, function (book) {
 callback(book);
 
 promise = $timeout(
 function () {
 refreshData(params, callback);
 }, 4000);
 });
 };
 
 refreshData(params, callback);
 }
 };

Slide 42

Slide 42 text

// book-info-website/public/javascripts/angular/services.js api.stopPushGet = function () {
 pushing = false;
 
 if (angular.isDefined(promise)) {
 $timeout.cancel(promise);
 promise = undefined;
 }
 };

Slide 43

Slide 43 text

Alternatives? • React • Backbone • Ember

Slide 44

Slide 44 text

Future of frontend development • Simpler than it looks • Has a lot of similarities with server side frameworks • Fast, as data is loaded after the bare minimum

Slide 45

Slide 45 text

Express Contender for the worlds most boring logo

Slide 46

Slide 46 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 47

Slide 47 text

Blocking Book Jacket API Rating API Book Info Website Book Info Website

Slide 48

Slide 48 text

Non-blocking Book Jacket API Rating API Book Info Website Book Info Website

Slide 49

Slide 49 text

– Express Demo “How likely is that it will break twice?”

Slide 50

Slide 50 text

// book-info-website/app.js
 var express = require('express');
 var path = require('path');
 var favicon = require('serve-favicon');
 var logger = require('morgan');
 var cookieParser = require('cookie-parser');
 var bodyParser = require('body-parser');
 
 var routes = require('./routes/index');
 var bookApi1 = require('./routes/book-api-v1');
 var bookApi2 = require('./routes/book-api-v2');
 
 var app = express();
 
 // view engine setup
 app.set('views', path.join(__dirname, 'views'));
 app.set('view engine', 'jade');
 
 // uncomment after placing your favicon in /public
 //app.use(favicon(__dirname + '/public/favicon.ico'));
 app.use(logger('dev'));
 app.use(bodyParser.json());
 app.use(bodyParser.urlencoded({ extended: false }));
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));
 app.use("/assets/modules", express.static(__dirname + '/bower_components'));
 
 app.use('/', routes);
 app.use('/api/v1', bookApi1);
 app.use('/api/v2', bookApi2);
 
 // catch 404 and forward to error handler
 app.use(function(req, res, next) {
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
 });
 
 // error handlers
 
 // development error handler
 // will print stacktrace
 if (app.get('env') === 'development') {
 app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
 message: err.message,
 error: err
 });
 });
 }
 
 // production error handler
 // no stacktraces leaked to user
 app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
 message: err.message,
 error: {}
 });
 });
 
 
 module.exports = app;

Slide 51

Slide 51 text

// book-info-website/app.js … 
 var routes = require('./routes/index');
 var bookApi1 = require('./routes/book-api-v1');
 var bookApi2 = require('./routes/book-api-v2'); … app.use("/assets/modules", express.static(__dirname + '/bower_components'));
 
 app.use('/', routes);
 app.use('/api/v1', bookApi1);
 app.use('/api/v2', bookApi2); 


Slide 52

Slide 52 text

// book-info-website/routes/index.js
 var express = require('express');
 var router = express.Router();
 
 /* GET home page. */
 router.get('/', function (req, res, next) {
 res.render('index');
 });
 
 /* GET home page. */
 router.get('/partials/details.html', function (req, res, next) {
 res.render('partials/details');
 });
 
 /* GET home page. */
 router.get('/partials/list.html', function (req, res, next) {
 res.render('partials/list');
 });
 
 
 module.exports = router;

Slide 53

Slide 53 text

// book-info-website/routes/index.js /* GET home page. */
 router.get('/', function(req, res, next) {
 res.render('index');
 }); … //- book-info-website/views/index.jade 
 doctype html
 html(lang="en" ng-app="booksApp")
 head
 title Book Info Website
 link(rel='stylesheet', href='/stylesheets/style.css')
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular-resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')
 script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')
 body
 div(ng-view)

Slide 54

Slide 54 text

// book-info-website/routes/book-api-v1.js
 var express = require('express');
 
 var booksApiClient = require('./../api-client/books');
 
 var router = express.Router();
 
 router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });
 
 router.get('/:isbn', function (req, res, next) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 res.json(bookDetail);
 });
 });
 
 module.exports = router;

Slide 55

Slide 55 text

// book-info-website/routes/book-api-v1.js router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });

Slide 56

Slide 56 text

// book-info-website/api-client/books.js
 var makeHttpRequest = require('./client');
 
 var Books = function () {
 
 var endpoint = "http://book-jacket-api.herokuapp.com/";
 
 this.getBook = function (isbn, callback) {
 makeHttpRequest(endpoint.concat(isbn), callback)
 };
 
 this.findAll = function (callback) {
 makeHttpRequest(endpoint, callback)
 };
 };
 
 module.exports = new Books();

Slide 57

Slide 57 text

// book-info-website/api-client/client.js var http = require('http');
 
 module.exports = function (url, callback) {
 http.get(url, function (res) {
 var str = '';
 
 res.on('data', function (chunk) {
 str += chunk;
 });
 
 res.on('end', function () {
 var apiResponse = JSON.parse(str);
 
 callback(null, apiResponse);
 });
 }).on('error', function (e) {
 callback(e);
 });
 };

Slide 58

Slide 58 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 59

Slide 59 text

–Async Demo “What’s the worst that can happen?”

Slide 60

Slide 60 text

// book-info-website/routes/book-api-v2.js
 
 var express = require('express');
 var async = require('async');
 var _ = require('underscore');
 
 var booksApiClient = require('./../api-client/books');
 var ratingsApiClient = require('./../api-client/ratings');
 
 var router = express.Router();
 
 router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });
 
 router.get('/:isbn', function (req, res, next) {
 async.parallel({
 'book': function (callback) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 callback(err, bookDetail);
 });
 },
 'rating': function (callback) {
 ratingsApiClient.getRating(req.params.isbn, function (err, ratings) {
 callback(err, ratings);
 });
 }
 }, function (err, results) {
 _.extend(results.book, {'rating': results.rating});
 res.json(results.book);
 });
 });
 
 module.exports = router;

Slide 61

Slide 61 text

// book-info-website/routes/book-api-v2.js router.get('/:isbn', function (req, res, next) {
 async.parallel({
 'book': function (callback) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 callback(err, bookDetail);
 });
 }, 
 'rating': function (callback) {
 ratingsApiClient.getRating(req.params.isbn, function (err, ratings) {
 callback(err, ratings);
 });
 } 
 }, function (err, results) {
 _.extend(results.book, {'rating': results.rating});
 res.json(results.book);
 });
 });


Slide 62

Slide 62 text

Alternatives? • PHP + Using Guzzle Futures • PHP + Queues • Go/Other language with parallelisation

Slide 63

Slide 63 text

Express • Non-blocking • Front-end Developer Friendly • Lightweight

Slide 64

Slide 64 text

In conclusion • Cross functional teams require knowledge of other disciplines • MVVM frameworks are going to get more popular • MVVM frameworks require APIs • Use middleware to hide none public services

Slide 65

Slide 65 text

http://www.happyjar.com/comic/pay-rise/

Slide 66

Slide 66 text

Any Questions? @PurpleBooth [email protected] https://joind.in/14285