Silex + Backbone + Handlebars

  • 429 views
Uploaded on

Доклад к "пятничным посиделкам"

Доклад к "пятничным посиделкам"

More in: Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
429
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
0
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Silex + Backbone + Handlebars делаем single page web application с RESTful бекендом на Silex
  • 2. Обзор на примере tester-environment Обзор на примере приложения для автотестов API в Enter
  • 3. Composer tool for dependency management in PHP • берет на себя разруливание зависимостей • помогает собрать приложение по кирпичикам • позволяет не думать об автозагрузке, поддерживая стандарты PSR-0 и PSR-4 Зачем? Для кого? • для тех, кто хочет контролировать «состав» своего приложения • для тех, кто не хочет тянуть в проект тяжелый фреймворк • для тех, кто хочет экспериментировать с компонентами • для тех, кто … Кто использует? • Symfony 2 (symfony/framework-standard-edition) • Yii 2 (yiisoft/yii2) • ZF 2 (zendframework/zendframework) • и другие
  • 4. packagist.org Основные консольные команды: self-update – для обновления самой тулзы init – для создания composer.json через консоль validate – для валидации composer.json install – для разрешения зависимостей update – для обновления зависимостей последние две учитывают composer.lock Основные инструкции: require – для описания зависимостей autoload – для описания правил автозагрузки скриптов scripts – для описания реакции на события
  • 5. Enter: тестовое окружение Как у нас? 1. { 2. "autoload": { 3. "psr-4": { 4. "EnterTesterEnvironment": "" 5. } 6. }, 7. "require": { 8. "silex/silex": "~1.1", 9. "twig/twig": "~1.0", 10. "symfony/twig-bridge": "~2.4", 11. "doctrine/dbal": "~2.4", 12. "doctrine/migrations": "1.0.*@dev", 13. "symfony/console": "~2.4" 14. }, 15. "scripts": { 16. "post-install-cmd": [ 17. "mkdir upload storage" 18. ] 19. } 20.} указываем версию: >=1.0 >=1.0,<2.0 >=1.0,<1.1 | >=1.2 1.0.* ~1.2 поддержка: PSR-4 PSR-0 Classmap – генерируем Files – включаем всегда события: *-install-cmd *-update-cmd *-status-cmd *-package-install и другие (всего 18)
  • 6. Backbone.js javascript microframework with a lot of features Что внутри: Events – предоставляет интерфейс для работы с событиями, может использоваться как Event Dispatcher Аналогия с MVC: Model – M Collection – M[] Router – C View – V
  • 7. Например 1. var Book = Backbone.Model.extend({…}); 2. var Library = Backbone.Collection.extend({ 3. model: Book, 4. url: '/api/v2/books' 5. }); 6. var Bookshelf = Backbone.View.extend({ 7. initialize: function () { 8. this.listenTo(this.collection, 'sync', this.render); 9. }, 10. render: function () {…} 11. }); 12. var Router = Backbone.Router.extend({ 13. routes: { 14. 'my/books': 'books' // #my/books 15. }, 16. books: function () { 17. var bookshelf = new Bookshelf({collection: new Library()}); 18. bookshelf.collection.fetch(); 19. } 20. }); 21. (new Router()) && Backbone.history.start({pushState: true});
  • 8. Underscore.js provides a whole mess of useful functional programming helpers Для чего: Предоставляет набор полезных функций для работы с • Collections • each • map • find, … • Arrays • union • intersection • difference • uniq, … • Functions • after • compose, … • Objects • extend, …
  • 9. Например 1. // Objects 2. _.extend({name : 'moe'}, {age : 50}); 3. => {name : 'moe', age : 50} 4. // Functions 5. var renderNotes = _.after(notes.length, render); 6. _.each(notes, function(note) { 7. note.asyncSave({success: renderNotes}); 8. }); 9. var greet = function(name){ return "hi: " + name; }; 10. var exclaim = function(statement){ return statement + "!"; }; 11. var welcome = _.compose(exclaim, greet); 12. welcome('moe'); 13. => 'hi: moe!' 14. // Arrays 15. _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); 16. => [1, 2] 17. // Collections 18. _.map([1, 2, 3], function(num){ return num * 3; }); 19. => [3, 6, 9] 20. _.map({one: 1, two: 2, three: 3}, function(num,key){ return num * 3; }); 21. => [3, 6, 9]
  • 10. Handlebars.js template engine for client side <script id="entry-template" type="text/x-handlebars-template"> <div class="entry"> <h1>{{title}}</h1> </div> </script> var source = $("#entry-template").html(); var template = Handlebars.compile(source); var context = {title: "Mustache на стероидах"} var html = template(context); $('body').html(html); 1 2 3 <body> <div class="entry"> <h1>Mustache на стероидах</h1> </div> </body> вместо тысячи слов
  • 11. Silex PHP microframework • Pimple в качестве DIC • собирается из компонентов Symfony 2 Что внутри: • идеален для микро-проектов • хорошая документация • проверенные временем компоненты • достаточно большое сообщество • покрыт тестами • легко расширяем Почему был выбран:
  • 12. Пример расширения 1. // Twig 2. $app->register(new SilexProviderTwigServiceProvider(), […]); 3. 4. $app->get('/hello/{name}', function ($name) use ($app) { 5. return $app['twig']->render('hello.twig', […]); 6. }); 7. // Swiftmailer 8. $app->register(new SilexProviderSwiftmailerServiceProvider(), […]); 9. 10. $app->post('/feedback', function () use ($app) { 11. $request = $app['request']; 12. 13. $message = Swift_Message::newInstance() 14. ->setSubject('[YourSite] Feedback') 15. ->setFrom(array('noreply@yoursite.com')) 16. ->setTo(array('feedback@yoursite.com')) 17. ->setBody($request->get('message')); 18. 19. $app['mailer']->send($message); 20. return new Response('Thank you for your feedback!', 201); 21. });
  • 13. FRONT END
  • 14. Инициализируем Front Controller: 1. <?php 2. define('ROOT', dirname(__DIR__)); 3. require ROOT . '/vendor/autoload.php'; 4. $app = new SilexApplication(); 5. require ROOT . '/app/config/application.php'; 6. require ROOT . '/app/config/components.php'; 7. require ROOT . '/app/config/routes.php'; 8. $app->run(); Структура проекта: /app /config /controllers /views /migrations /vendor /public /assets /js /css
  • 15. app / config / components.php 1. <?php 2. /** @var SilexApplication $app */ 3. $app->register(new SilexProviderTwigServiceProvider(), ['twig.path' => ROOT . '/app/views']); 4. $app->register(new SilexProviderUrlGeneratorServiceProvider()); 5. $app->register( 6. new SilexProviderDoctrineServiceProvider(), 7. [ 8. 'db.options' => array( 9. 'driver' => 'pdo_sqlite', 10. 'path' => ROOT . '/storage/tester_environment.db', 11. ), 12. ] 13. ); app / config / routes.php 1. <?php 2. /** @var SilexApplication $app */ 3. $app->mount('/', require ROOT . '/app/controllers/pages.php'); 4. $app->mount('/api', require ROOT . '/app/controllers/api.php'); Конфигурация приложения
  • 16. app / controllers / pages.php 1. <?php 2. /** 3. * @var SilexApplication $app 4. * @var SilexControllerCollection $pages 5. */ 6. $pages = $app['controllers_factory']; 7. $pages->get('/', function () use ($app) { 8. /** @var Twig_Environment $twig */ 9. $twig = $app['twig']; 10. return $twig->render( 11. 'layout.twig', 12. [ 13. 'title' => 'Enter: тестовое окружение', 14. 'styles' => [ 15. '//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css', 16. '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css', 17. '/assets/css/main.css', 18. ], 19. 'scripts' => [ 20. '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js', 21. '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js', 22. ], 23. 'content' => $twig->render('home.twig'), 24. ] 25. ); 26. } 27. )->bind('home'); 28. $pages->get('/schemas', function () use ($app) {…})->bind('schemas'); 29. $pages->get('/endpoints', function () use ($app) {…})->bind('endpoints'); 30. $pages->get('/batch-testing', function () use ($app) {…})->bind('batch-testing'); 31. $pages->get('/compare-testing', function () use ($app) {…})->bind('compare-testing'); 32. return $pages;
  • 17. Twig и Handlebars без проблем {% verbatim %} <script id="test-case-template" type="text/x-handlebars-template"> <h5 class="title"><span class="status"><i class="{{ icon }}"></i></span> {{ title }}</h5> {{#if result}} <div class="result {{ class }}">{{{ result }}}</div> {{/if}} </script> <script id="test-case-result-template" type="text/x-handlebars-template"> {{#if result}} <div class="panel-heading">… <i class="fa fa-code pull-right"></i></div> <div class="panel-body">{{ result }}</div> {{/if}} </script> {% endverbatim %} Правило хорошего тона в JS <script> (function (w) { w.Enter = { api: { parse_csv: '{{ path('api.parse-csv') }}', run_test: '{{ path('api.run-test') }}', api_endpoints: '{{ path('api.api-endpoints') }}', json_schemas: '{{ path('api.json-schemas') }}' } }; }(window)); </script>
  • 18. public / assets / js / api-endpoints.js 1. (function (n, $, _, b, t) { 2. var templateApiEndpoint = t.compile($('#api-endpoint-template').html()), 3. templateApiEndpoints = t.compile($('#api-endpoints-template').html()); 4. n.ApiEndpoint = b.Model.extend({ 5. urlRoot: n.api.api_endpoints 6. }); 7. n.ApiEndpoints = b.Collection.extend({ 8. url: n.api.api_endpoints, 9. model: n.ApiEndpoint, 10. … 11. }); 12. n.ApiEndpointView = b.View.extend({ 13. template: templateApiEndpoint, 14. events: { 15. 'click .save': 'save', 16. 'click .delete': 'destroy' 17. }, 18. … 19. }); 20. n.ApiEndpointsView = b.View.extend({…}); 21. }(window.Enter, window.jQuery, window._, window.Backbone, window.Handlebars)); Организация модулей public / assets / js / endpoints.js 1. (function (n) { 2. var apiEndpoints = new n.ApiEndpoints(), 3. apiEndpointsView = new n.ApiEndpointsView({collection: apiEndpoints}); 4. apiEndpoints.fetch({success: function () { 5. apiEndpointsView.render(); 6. }}); 7. }(window.Enter));
  • 19. public / assets / js / drop-csv.js 1. (function (w, n) { 2. var zone = new w.FileDrop('drop', {}); 3. zone.event('send', function (files) { 4. // пока работаем только с одним файлом 5. var file = files[0]; 6. file.event('done', function (xhr) { 7. n.Bus.trigger('uploaded', n.Formatter.parse(xhr.responseText)); 8. }); 9. file.sendTo(n.api.parse_csv); 10. }) 11. }(window, window.Enter)); Взаимодействие между модулями public / assets / js / tools.js 1. (function (w, n, _, b) { 2. n.DEBUG = n.DEBUG || true; 3. n.Bus = _.extend({}, b.Events); 4. … 5. }(window, window.Enter, window._, window.Backbone)); public / assets / js / batch-testing.js 1. (function (w, n, $) { 2. … 3. n.Bus.on('uploaded', function (test_cases) { 4. testCases = new n.TestCases(test_cases); 5. testCasesView = new n.TestCasesView({collection: testCases}); 6. }); 7. }(window, window.Enter, window.jQuery));
  • 20. BACK END
  • 21. app / controllers / api.php 1. <?php 2. 3. use SymfonyComponentHttpFoundationRequest; 4. 5. /** 6. * @var SilexApplication $app 7. * @var SilexControllerCollection $api 8. */ 9. $api = $app['controllers_factory']; 10. $api->post('/parse-csv', function () use ($app) {…})->bind('api.parse-csv'); 11. $api->post('/run-test', function (Request $request) use ($app) {…})->bind('api.run-test'); 12. $api->get('/api-endpoints', function () use ($app) {…})->bind('api.api-endpoints'); 13. $api->post('/api-endpoints', function (Request $request) use ($app) {…}); 14. $api->get('/api-endpoints/{id}', function ($id) use ($app) {…})->bind('api.api-endpoint'); 15. $api->put('/api-endpoints/{id}', function ($id, Request $request) use ($app) {…}); 16. $api->delete('/api-endpoints/{id}', function ($id) use ($app) {…}); 17. $api->get('/json-schemas', function () use ($app) {…})->bind('api.json-schemas'); 18. $api->post('/json-schemas', function (Request $request) use ($app) {…}); 19. $api->get('/json-schemas/{id}', function ($id) use ($app) {…})->bind('api.json-schema'); 20. $api->put('/json-schemas/{id}', function ($id, Request $request) use ($app) {…}); 21. $api->delete('/json-schemas/{id}', function ($id) use ($app) {…}); 22. return $api; Здесь все просто
  • 22. Спасибо за внимание… наверное @ikamilsk, www.kamilsk.com