$30 off During Our Annual Pro Sale. View Details »

MVVM and Silex

Billie Thompson
May 13, 2015
1.3k

MVVM and Silex

PHPTour

Billie Thompson

May 13, 2015
Tweet

Transcript

  1. 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}

    View Slide

  2. MVVM and
    Silex
    Welcome to the world of
    tomorrow!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. 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}

    View Slide

  10. Silex
    Solid & simple

    View Slide

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

    View Slide

  12. –Silex Demo
    “Please don’t break”

    View Slide

  13. // book-jacket-api/public/index.php

    use PurpleBooth\Controller\BooksController;


    require_once __DIR__.'/../vendor/autoload.php';


    $app = new Silex\Application();

    $app->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();

    View Slide

  14. // book-jacket-api/public/index.php


    require_once __DIR__.'/../vendor/autoload.php';


    $app = new Silex\Application();

    …


    $app->run();

    View Slide

  15. // book-jacket-api/public/index.php

    $app->get('/hello/{name}', function($name) use($app) {

    return 'Hello '.$app->escape($name);

    });


    View Slide

  16. // 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");

    View Slide

  17. // book-jacket-api/src/Controller/BooksController.php

    namespace PurpleBooth\Controller;


    use Symfony\Component\HttpFoundation\JsonResponse;


    class BooksController

    {


    private $bookData = […];


    public function indexAction()

    {

    return new JsonResponse(array_values($this->bookData));


    }


    public function getAction($isbn)

    {

    if (!array_key_exists($isbn, $this->bookData)) {

    return new JsonResponse(null, 404);

    }


    return new JsonResponse($this->bookData[$isbn]);

    }

    }

    View Slide

  18. // book-jacket-api/src/Controller/BooksController.php


    namespace PurpleBooth\Controller;


    use Symfony\Component\HttpFoundation\JsonResponse;


    class BooksController

    {


    …

    public function getAction($isbn)

    {

    if (!array_key_exists($isbn, $this->bookData)) {

    return new JsonResponse(null, 404);

    }


    return new JsonResponse($this->bookData[$isbn]);

    }

    }

    View Slide

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

    View Slide

  20. Alternatives?
    • Apigility
    • ZF2
    • Symfony

    View Slide

  21. MVVM
    Model, View and View Model

    View Slide

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

    View Slide

  23. MVVM Framework
    View
    View
    Model
    Model
    Remote
    API
    Browser Server

    View Slide

  24. View Slide

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

    View Slide

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

    View Slide





  27. Book Info Website















    View Slide

  28. //- 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')

    View Slide

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

    script(src='/javascripts/angular/services.js')

    script(src='/javascripts/angular/controllers.js')

    View Slide

  30. // 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: '/'

    });

    }]);

    View Slide

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

    ]);

    View Slide

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

    View Slide

  33. View Slide

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

    View Slide

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

    });

    }

    ]

    );

    View Slide

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

    }]
    );

    View Slide

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

    }]);

    View Slide

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

    }]);


    View Slide

  39. MVVM Framework
    View
    View
    Model
    Model
    Remote
    API
    Browser Server

    View Slide

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

    });

    }

    ]

    );

    View Slide

  41. // 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);

    }

    };

    View Slide

  42. // book-info-website/public/javascripts/angular/services.js
    api.stopPushGet = function () {

    pushing = false;


    if (angular.isDefined(promise)) {

    $timeout.cancel(promise);

    promise = undefined;

    }

    };

    View Slide

  43. Alternatives?
    • React
    • Backbone
    • Ember

    View Slide

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

    View Slide

  45. Express
    Contender for the worlds
    most boring logo

    View Slide

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

    View Slide

  47. Blocking
    Book Jacket
    API
    Rating API
    Book Info
    Website
    Book Info
    Website

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. // 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);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. // book-info-website/routes/book-api-v1.js
    router.get('/', function (req, res, next) {

    booksApiClient.findAll(function (err, books) {

    res.json(books);

    });

    });

    View Slide

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

    View Slide

  57. // 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);

    });

    };

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. // 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);

    });

    });


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide