Post on 27-Aug-2014
description
transcript
Symfony2 and AngularJS
Antonio Perić-Mažar
16.05.2014, PHPday Verona
https://joind.in/talk/view/11290
About me• Antonio Perić-Mažar,
mag. ing. comp.• CEO @ locastic• Software developer, Symfony2• Sylius Awesome Contributor :)
• www.locastic.com• antonio@locastic.com• twitter: @antonioperic
Who we are?• locastic (www.locastic.com)• Web and mobile development• UI/UX design• Located in Split, Croatia
Our works?
Symfony2 PHP Framework, server side MVC, HTTP Toolbox, methodology One of the most advanced PHP framework Strong community Easy to test it
Symfony2 Bundles Components Routing Templating (Twig) Forms etc
AngularJS Client-side JavaScript framework Prescriptive, MVC (MVVM) Makes creating UI easier thought data-binding Good organizing and architecture Learning curve, easy to start hard to master $scope, modules, controllers, providers, services
AngularJS CoreTwo-way data binding
JS:<span id=”someId”></span>document.getElementById('someId').text = 'locastic';
NG<span>{{someName}}<span>
var someName = 'locastic';
AngularJS Core
<html ng-app>
<head>
<script src=”angular.js”></script>
<script src=”controller.js”></script>
<head>
<body>
<div ng-controller=”HelloController”>
<p>{{ greeting.text}}, World</p>
</div>
</body>
</html>
// controller.js
function HelloController($scope) {
$scope.gretting = {text: 'Hello'}
}
HTML template
AngularJS Core Deep Linking Dependency Injection Directives
<div class=”container”>
<div class=”inner>
<ul>
<li>Item
<div class=”subitem”>Item2</div>
</li>
</ul>
</div>
</div>
<dropdown>
<item>Item 1>
<subitem >Item 2</subitem>
</item>
</dropdown>
Testable
+
SPA (Singe Page App)
SPA Aka SPI (Single Page interface) desktop apps UX HTML / JS / CSS / etc in single page load fast AJAX and XHR
SPA Arhitecture
Backend (rest api) with Symfony2Frontend with AngularJs
Separation or combination?
UI == APP
Symfony2 AngularJS
Usage, language Backend, PHP Frontend, Javascript
Dependency Injection Yes Yes
Templating Twig HTML
Form component Yes Yes
Routing component Yes Yes
MVC Yes Yes
Testable Yes Yes
Services Yes Yes
Events Yes Yes
i18n Yes Yes
Dependency management Yes Yes
etc ... ...
RESTful wsSimpler than SOAP & WSDLResource-oriented (URI)
Principles:
HTTP methods (idempotent & not) stateless directory structure-like URIs XML or JSON (or XHTML)
GET (vs HEAD), POST, PUT (vs PATCH), DELETE, OPTIONS
Building Rest Api with SF2
There is bundle for everything in Sf2. Right?So lets use some of them!
Building Rest Api with SF2What we need?JMSSerializerBundle FOSRestBundleNelmioApiDocBundle
Building Rest API with SF2JMSSerializerBundle (de)serialization via annotations / YAML / XML / PHP integration with the Doctrine ORM handling of other complex cases (e.g. circular references)
Building Rest Api with SF2Locastic\Bundle\TodoBundle\Entity\Todo:
# exclusion_policy: ALL exclusion_policy: NONE properties:# description:# expose: true createdAt:# expose: true exclude: true deadline: type: DateTime<'d.m.Y. H:i:s'># expose: true done:# expose: true serialized_name: status
Building Rest Api with SF2fos_rest:
disable_csrf_role: ROLE_API
param_fetcher_listener: true
view:
view_response_listener: 'force'
formats:
xml: true
json: true
templating_formats:
html: true
format_listener:
rules:
- { path: ^/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true }
exception:
codes:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
messages:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
allowed_methods_listener: true
access_denied_listener:
json: true
body_listener: true
Building Rest Api with SF2/**
* @ApiDoc( * resource = true, * description = "Get stories from users that you follow (newsfeed)", * section = "Feed", * output={ * "class" = "Locastic\Bundle\FeedBundle\Entity\Story" * }, * statusCodes = { * 200 = "Returned when successful", * 400 = "Returned when bad parameters given" * } * ) * * @Rest\View( * serializerGroups = {"feed"} * ) */public function getFeedAction(){ $this->get('locastic_auth.auth.handler')->validateRequest($this->get('request'));
return $this->getDoctrine()->getRepository('locastic.repository.story')->getStories($this->get('request')->get('me'));}
Building Rest Api with SF2
Templating
TWIG <3
Templating<!DOCTYPE html>
<html> <head> <meta charset="UTF-8" /> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %} <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
{#<link rel="stylesheet" href="{{ asset('css/normalize.css') }}">#} <link rel="stylesheet" href="{{ asset('css/main.css') }}">
<!-- load bootstrap and fontawesome via CDN --> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" /> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css" />
<script src="{{ asset('js/vendor/modernizr-2.6.2.min.js') }}"></script> {% endblock %} <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script> <script src="https://code.angularjs.org/1.2.16/angular-route.min.js"></script> <script src="{{ asset('js/main.js') }}"></script>
{% endblock %} </body></html>
TemplatingProblem:{{ interpolation tags }} - used both by twig and AngularJS
Templating{% verbatim %} {{ message }}{% endverbatim %}
Templating
var phpDayDemoApp = angular.module('phpDayDemoApp', [],
function($interpolateProvider) {
$interpolateProvider.startSymbol('[['); $interpolateProvider.endSymbol(']]');});
Now we can use{% block content %}
[[ message ]] {# rendered by AngularJS #}
{% end block %}
Tweak Twig lexer delimiters? Bad idea.
TemplatingUsing assetic for minimize
{% javascripts
"js/angular-modules/mod1.js" "#s/angular-modules/mod2.js" "@AngBundle/Resources/public/js/controller/*.js"
output="compiled/js/app.js"
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
TemplatingUsing assetic for minimize
Since Angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for PhoneListCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.
Use an inline annotation where, instead of just providing the function, you provide an array. This array contains a list of the service names, followed by the function itself.
function PhoneListCtrl($scope, $http) {...}phonecatApp.controller('phpDayCtrl', ['$scope', '$http', PhoneListCtrl]);
Managing routesClient side:
ngRoute
independent since Angular 1.1.6
hashbang #! & HTML5 mode
<base href="/">
$locationProvider
.html5Mode(true)
.hashPrefix('!');
Managing routeshttp://localhost/todoshttp://localhost/#todos
Resolving conflictsFallback, managing 404
Managing routesClient side:
// module configuration...$routeProvider.when('/todos/show/:id', { templateUrl : 'todo/show', controller : 'todoController'})
// receive paramssfugDemoApp.controller('todoController', function($scope, $http, $routeParams){
$scope.todo = {};
$http .get('/api/todo/show/' + $routeParams.id) .success(function(data){ $scope.todo = data['todo']; });
});
Managing routesServer side: locastic_rest_todo_getall:
pattern: /api/get-all defaults: _controller: LocasticRestBundle:Todo:getAll
locastic_rest_todo_create: pattern: /api/create defaults: _controller: LocasticRestBundle:Todo:create
locastic_rest_todo_show: pattern: /api/show/{id} defaults: _controller: LocasticRestBundle:Todo:show
TranslationsAngularJS has its own translation system I18N/L10N . But it might be interesting to monitor and centralize translations from your backend Symfony.
FormsSymfony Forms <3We don't want to throw them awayBuild custom directive
FormssfugDemoApp.directive('ngToDoForm', function() {
return { restrict: 'E', template: '<div class="todoForm">Form will be!</div>' }});
'A' - <span ng-sparkline></span>'E' - <ng-sparkline></ng-sparkline>'C' - <span class="ng-sparkline"></span>'M' - <!-- directive: ng-sparkline →
Usage of directive in HTML:<ng-to-do-form></ng-to-do-form>
FormssfugDemoApp.directive('ngToDoForm', function() {
return { restrict: 'E', templateUrl: '/api/form/show.html' }});
FormssfugDemoApp.directive('ngToDoForm', function() {
return { restrict: 'E', templateUrl: '/api/form/show.html' }});
locastic_show_form: pattern: /form/show.html defaults: _controller: LocasticWebBundle:Default:renderForm
public function renderFormAction(){ $form = $this->createForm(new TodoType());
return $this->render('LocasticWebBundle::render_form.html.twig', array( 'form' => $form->createView() ));}
FormsSuprise!!!
FormTemplate behind directive
<form class="form-inline" role="form" style="margin-bottom: 30px;"> Create new todo: <div class="form-group"> {{ form_label(form.description) }} {{ form_widget(form.description, {'attr': {'ng-model': 'newTodo.description', 'placeholder': 'description', 'class': 'form-control'}}) }} </div> <div class="form-group"> <label class="sr-only" for="deadline">Deadline</label> <input type="text" class="form-control" id="deadline" placeholder="deadline (angular-ui)" ng-model="newTodo.deadline"> </div> <input type="button" class="btn btn-default" ng-click="addNew()" value="add"/></form>
TestingSymfony and AngularJS are designed to test. So write test
BehatPHPUnitPHPSpecJasmine
…Or whatever you want just write tests
SummaryThe cleanest way is to separate backend and frontend. But there is some advantages to use both together.
Twig + HTML works well.
Assetic Bundle is very useful to minify bunches of Javascript files used by AngularJs
Translation in the template. the data in the API payload does not need translation in most cases. Using Symfony I18N support for the template makes perfect sense.
Loading of Option lists. Say you have a country list with 200+ options. You can build an API to populate a dynamic dropdown in angularjs, but as these options are static, you can simply build that list in twig. Forms in Symfony are pretty cool
Take advantage of the authentication feature of Symfony. You can used the same authenticated session to the API for the application, too. No need to use Oauth.
And remember
Keep controllers small and stupid, master Dependency injection, delegate to services and events.
Thank you!
QA
Please rate my talkhttps://joind.in/talk/view/11290
follow me on twitter: @antonioperic