Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MVVM and Silex

3a1c6ce62064c58e2420e9e27c125716?s=47 Billie Thompson
May 13, 2015
1.1k

MVVM and Silex

PHPTour

3a1c6ce62064c58e2420e9e27c125716?s=128

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}
  2. MVVM and Silex Welcome to the world of tomorrow!

  3. Goals • Working knowledge • JavaScript serverside • Client side

    JavaScript frameworks • Know how to integrate them with PHP • Become more of a Cross Stack developer
  4. Who’re you? Billie Thompson @PurpleBooth billie@purplebooth.co.uk PHP Contractor

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

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

    Website Browser
  7. Book Information Service DB Queues Logic Responsive Extendible Clean Parallel

    Serve JS Frontend-y Book Jacket API Rating API Book Info Website Browser
  8. Book Information Service Silex PHP AngularJS JavaScript Express JavaScript Book

    Jacket API Rating API Book Info Website Browser
  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}
  10. Silex Solid & simple

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

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

  13. // book-jacket-api/public/index.php <?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();
  14. // book-jacket-api/public/index.php
 
 require_once __DIR__.'/../vendor/autoload.php';
 
 $app = new Silex\Application();


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


    });
 …
  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");
  17. <?php
 // 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]);
 }
 }
  18. <?php
 // 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]);
 }
 }
  19. Silex for the business logic • Rapid API Development •

    Familiar libraries (Symfony, etc) • Perfect for small services
  20. Alternatives? • Apigility • ZF2 • Symfony

  21. MVVM Model, View and View Model

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

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

  24. None
  25. –AngularJS Demo “It looked so easy when I saw someone

    else’s talk”
  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)
  27. <!DOCTYPE html>
 <html lang="en" ng-app="booksApp">
 
 <head>
 <title>Book Info Website</title>


    <link rel="stylesheet" href="/stylesheets/style.css">
 <script src="/assets/modules/angular/angular.js"></script>
 <script src="/assets/modules/angular-resource/angular-resource.js"></script>
 <script src="/assets/modules/angular-route/angular-route.js"></script>
 <script src="/javascripts/angular/app.js"></script>
 <script src="/javascripts/angular/services.js"></script>
 <script src="/javascripts/angular/controllers.js"></script>
 </head>
 
 <body>
 <div ng-view></div>
 </body>
 
 </html>
  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')

  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')

  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: '/'
 });
 }]);
  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'
 ]); 

  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)
  33. None
  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}}
  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();
 });
 }
 ]
 );
  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();
 }] ); …
  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;
 }]);
  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;
 }]);

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

  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();
 });
 }
 ]
 );
  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);
 }
 };
  42. // book-info-website/public/javascripts/angular/services.js api.stopPushGet = function () {
 pushing = false;


    
 if (angular.isDefined(promise)) {
 $timeout.cancel(promise);
 promise = undefined;
 }
 };
  43. Alternatives? • React • Backbone • Ember

  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
  45. Express Contender for the worlds most boring logo

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

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

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

    Info Website
  49. – Express Demo “How likely is that it will break

    twice?”
  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;
  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); 

  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;
  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)
  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;
  55. // book-info-website/routes/book-api-v1.js router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err,

    books) {
 res.json(books);
 });
 });
  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();
  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);
 });
 };
  58. Book Information Service Book Jacket API Rating API Book Info

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

  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;
  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);
 });
 });

  62. Alternatives? • PHP + Using Guzzle Futures • PHP +

    Queues • Go/Other language with parallelisation
  63. Express • Non-blocking • Front-end Developer Friendly • Lightweight

  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
  65. http://www.happyjar.com/comic/pay-rise/

  66. Any Questions? @PurpleBooth billie@purplebooth.co.uk https://joind.in/14285