Symfony2 and AngularJS

Post on 27-Aug-2014

3,715 views 6 download

description

These are my slide from talk at PHPDay in Verona 20014. Forget about classic website where UX is not so important. We are living in time where usability is one of the important thing if you are building some business client oriented web service. In locastic we are working on CRM that is based on Symfony2 as backend and AngularJS as frontend solution. How to connect this two frameworks? What are best practices? What are disadvantageous? How to take best from both worlds? These are topics I will cover in my talk with real examples.

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