dive into
TRANSCRIPT
Dive into
http://blog.nerdery.com/2013/06/symfony2-tech-talk/
Maxwell VanderveldeWho the hell are you?
Developer at The Nerdery
● Working with
o PHP (Zend, Symfony2, Cake)
o Android, Javascript
o Tools! Git, Jenkins
● Kickstarting Internal Symfony2 Development @ The Nerdery
Chris JollyWho the hell are you?
CTO at Ontraq Europe
● Working with
o PHP (Symfony2, Drupal, OXID)
o Javascript
o .Net (MVC, WebApi, Forms)
● We do
o Integration, Interfaces & SSO
What's inside
● Focus on high level birds eye view of framework
● Reinforce low level architecture
● Provide prerequisite knowledge
Code
Architecture / Components
Orientation / Principles
let's get started!
What is Symfony2?
"First, Symfony2 is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems."
"... Then, based on these components, Symfony2 is also a full-stack web framework."
What is Symfony2?
Is Symfony2 an MVC Framework?
"Symfony2 is never defined as being an MVC framework"
"... I really don't care whether Symfony2 is MVC or not."
"... The separation of concerns is all I care about.”
That’s one way of describing it, but…
In fact it’s very similar to MS .Net MVC + Entity Framework
What is Symfony2?The original Blog Post:
http://fabien.potencier.org/article/49/what-is-symfony2
How to Build a Framework from the Components:
http://fabien.potencier.org/article/50/create-your-own-framework-on-top-of-the-symfony2-components-part-1
“Getting Started” Documentation
http://symfony.com/doc/current/quick_tour/the_big_picture.html
Symfony2 Principles
Separate Concerns
● Do one thing,
● Make it excellent.
● Stay out of the way.
"Design programs to be connected to other programs." - Rule 3, Unix Philosophy
Symfony2 Principles
Break the chain
● Composition, not inheritance
● Event and DI driven
Symfony2 Principles
Be Explicit, not Implicit.
● Not 'on rails'
● Improves Flexibility
● Improves Readability
Explicit is better than implicit.Simple is better than complex.
Complex is better than complicated.- Zen of Python
Symfony2 Principles
Follow Current Standards
● Uses PHP namespacing
o Follows PSR-0 Autoloading standard
● Follows HTTP Specification
"Namespaces are one honking great idea -- let's do more of those!" - Zen of Python
Symfony2 Principles
Don't reinvent the wheel
● Use The right tool for the job
o Uses Doctrine as an ORM
o Uses Composer as Dependency management
o Uses Twig as a View templating engine
● Uses a healthy ecosystem of 3rd-party bundles
Symfony2 Principles
Decouple your code
● Replace the part, not the machine
● Make code as independent as possible
● Allow for interaction on common interfaces
"The old adage "don't reinvent the wheel" doesn't apply when the wheel comes attached to a locomotive engine."
Symfony2 Principles
Tooling Agnostic
● Each piece of the framework is replaceable
o ORM
o View templating engine
Distrust all claims for “one true way” - Rule 16, Unix Philosophy
What is Symfony2?
What framework model is Symfony2?
“Symfony2 is an HTTP Request -> Response framework”
Or…
“the HttpFoundation Component does a great job handling the Request -> Response process.”
Symfony2's Foundation
Request Response
Symfony2's Foundation
Request Response
HttpFoundation Component
● HttpFoundation defines an object-oriented layer for the HTTP specification.
● It protects the programmer from the real world nastiness
● You can use it stand-alone if wanted
● http://symfony.com/doc/current/components/http_foundation/introduction.html
● index.htmlhttps://github.com/symfony/HttpFoundation
HttpFoundation\Request Object
● Container for a standard HTTP request
● Created from PHP globals
HttpFoundation\Response Object
● Holds all the information that needs to be sent back to the client from a given request.
o Content, Status code, HTTP headers
HttpFoundation Demo
Let’s go OO…
Symfony2's Foundation
Request Response
HttpKernel Component
● First object created. The core of the web framework
● Takes in a Request, outputs a Response
● Binds the components together and controls the flow
Symfony2's Foundation
Request Response
HttpKernel
EventDispatcher
● Symfony's internals are event driven
● Controlled by the EventDispatcher Component
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
EventDispatcher
● Implements the Observer pattern
o Listeners are added to an event
o Dispatcher is notified to dispatch the event
o Each Listener is notified / called
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
Listeners
EventDispatcher
What does it mean to be event driven?
Event A
Event B
Event C
Listener
Listener
Listener
Listener
Listener
The kernel.request event
● HttpKernel first calls the kernel.request event
● Listeners of the object are called
o These listeners may return a Response
DependencyInjection
● Allows you to standardize and centralize the way objects are constructed in your application.
● Connects / wires objects together
o Defined in a yaml/xml config
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
Listeners
DependencyInjection
Routing
● Given a Request
o Defined in Yaml or Annotations
o Determines route based on URI and Request Type
o Returns a controller to execute
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
Listeners
DependencyInjection
Routing
The kernel.controller event
● HttpKernel calls the kernel.controller event BEFORE calling the controller
● Listeners of the object are called
o These listeners may return a Response
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
Controller
Listeners
DependencyInjection
Routing
Controller
● Take a request, may return a response
● Nothing Special
o can be any PHP callable
The kernel.view event
● What if the controller doesn't return a response?
● After the controller is called the kernel.view event is dispatched
o These listeners may return a Response
Symfony2's Foundation
Request Response
HttpKernelEventDispatche
r
Controller
Listeners
DependencyInjection
Routing
The kernel.exception event
● Called on uncaught exception
● Called on unhandled request
● the kernel.exception event is dispatched
o Application's last chance to return a Response
EventDispatcher
● You can make your own listeners
● Can dispatch your own events
Using Composer
What is Dependency Management?
● Your project needs things (Doctrine, Symfony, Twig)
o Those things need things
● Composer installs all the things!
Using Composer
Composer is not a package manager!
● Familiar with PEAR/PEAR2/PECL?
o These install libraries globally
o Runs into conflicts if you need two different versions
● Composer installs on per-project
Using Composer
● Composer installs dependencies in /vendors/
● Provides an autoloader for classes
● Will "lock" each dependency down to a version
o Each subsequent insall will use the same version
Using Composer
● Composer allows libraries to be excluded from Source Control
o Keeps project lean
o Abstracts wiring of dependencies
o Easy to manage installs & updates:
composer.phar install
composer.phar update
Using Composer
How does it work?
● Composer is a php executable (.phar file)
o Can be included on an individual project basis
o Can be installed on the system
● Dependencies are defined in a json file
Using Composer
{ "require": { "symfony/symfony": "2.1.*", "doctrine/orm": ">=2.2.3,<2.5-dev", },}
$ curl -sS https://getcomposer.org/installer | php --install-dir=bin
$ composer require symfony/http-foundation
The Bundle System
Symfony2 project code is broken into "bundles"
The Bundle System
● What is a bundle?
ForumBundle
AdminBundle
BlogBundle
WebBundle}
● Structured set of files that implement a single feature of your application.
● Every project is a bundle in Symfony2
The Bundle System
● Bundles should be self contained
● Bundles can extend other bundles
Symfony Configuration
● Configuration kept in yaml files
o Prod, Dev, & Test environments.
Symfony Configuration
● Routing and others can be defined with annotationnamespace Acme\HelloBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
class HelloController { /** * @Route("/hello/{name}") * @Method("GET") */ public function indexAction($name) { ... }}
Symfony Parameters
● Environment specific configurations?
o (Passwords, keys, etc.)
o Kept in a parameters.yml file
o Can be kept excluded from Version Control
o Replaces placeholders in configuration files
Symfony Parameters
imports:
- { resource: parameters.yml }
database:
driver: MySQL
password: %db_password%
username: %db_username%Parameter placeholders
config.yml
Symfony Parameters
parameters:
db_username: "admin"
db_password: "sadjiou34iu3$as"
Placeholder values
parameters.yml
The Symfony2 View
● Layouts are created using Twig Templating engine
o May also use php, or another template engine
The Symfony2 View
● Why use Twig?
o Isn't PHP a templating language?
● View helpers are object oriented
o Creates a lot of noise in the view
● Twig aims to clean the noise up
● It’s a lot like Smarty
Templates are broken into a block structure
The Symfony2 View
layout.twig myView.twig
Header
Body Body
Header
● Partial views can be included easily
● Twig outputs can be "filtered"
● Output Autoescaped by default
The Symfony2 View
How do filters work?
The Symfony2 View
*Hello Damn World*
Markdown Filter
<b>Hello Damn World</b>
Expletive Filter
<b>Hello D*mn World</b>
Initial output
final output
Javascript and CSS asset compilation!
● Can combine multiple CSS, JS into single output files
● Can Filter output!
The Symfony2 View
Templating is slow!
● Templates cached in production!
The Symfony2 View
Using Doctrine
● Symfony's default database ORM
● Follows strict standards for data entity management
Using Doctrine
● Data models are Entity objects
o Plain objects
● Data structure defined as annotations on Entities
Using Doctrine
● Entities are accessed through Doctrine Repositories
o May be customized for custom queries
Using Doctrine
Entity
EntityRepository
CustomRepository
Database
Using Doctrine
● Queries are optimized
o Allows for cached Queries
● Associations are lazy loaded
o Reduces overhead
Application Security
Security Component
Authentication
Authorization● Identify a user
o Formo Http Autho Custom?
● Resource accesso URLo Entityo Action/Method
Security Component
Firewalls
● Does a user need to be authenticated?
● Based on URLs
Security Component
Anonymous User
Firew
all
/foo
Security Component
Anonymous User
Firew
all
/foo
/foo
Anonymous users are allowed!
Security Component
Anonymous User
Firew
all
/foo
/foo
/login
No Anonymous users, try logging in!
Security Component
Access Controls
● Can a user access a resource?
● Based on user
● Permissions distributed as "roles"
Security Component
Anonymous User
Firew
all
/foo
/foo
/login
Acce
ss Con
trol
No Required Roles
/foo
Security Component
Anonymous User
Firew
all
/foo
/foo
Acce
ss Con
trol User needs Admin Role!
/foo
/loginTry logging in!
Security Component
User "Ryan"
Firew
all
/foo
/foo
Acce
ss Con
trol
/foo
User is logged in and has Admin role
Security Component
User "Ryan"
Firew
all
/foo
/foo
Acce
ss Con
trol
User is missing Admin Role!
/foo
403
Already logged in
Symfony Forms
Forms are a trouble point for most frameworks
● Separation of Concerns
● Decouple from view
● Decouple from entities
● Validation
Symfony Forms
● Form Builder
o Add fields by name, type, etc.
● Form types
● Bind to gather data
Symfony Forms
Validation
● Forms don't validate
● Check entity state after form populates it.
Symfony Forms
How does Symfony2 organize forms?
● Each form is broken into many forms
● Forms are structured in a tree
Symfony Forms
Symfony Forms
Symfony FormsForm
[New Employee]
Text[Name]
Number[salary]
BirthDate[birth date]
int[Year]
int[Month]
int[Day]
Symfony Forms
How do decorators work?
● Leverage template block structure
● Broken into "widgets"
o Each widget gets a view block / partial
Symfony Forms
● Benefits
o View logic detached from Form!
o Same form, different views
Symfony Forms
What about the Data?
Got a complex form?
Want some more control over form data types?
Symfony Forms
Form data is stored in three formats
o Model
o Normalized
o View
Symfony Forms
Wait... what?
Model Normalized View
● Tied to Entity● Must associate
with property
● Tied to input type● Reliable for logic
● Tied to view● Stringly typed
Symfony Forms
● Why all the formats?
o Decouples View from Model
o Decouples Domain logic from View & Model
Symfony Forms
Form Transformers
Symfony Forms
Form Transformers
● Used to convert one format to another
● Bijective transformation
Symfony Forms
setData($data);
Model Normalized ViewTransform Transform
Symfony Forms
bind($data);
Model Normalized View
Reverse Transform
Reverse Transform
Symfony Console
Symfony Console
● Provides Useful commands
o Cache options
o Code Generation
o Asset Symlinking
o Custom Commands
Symfony Console
Custom Commands
● Service oriented
o Get Database or anything the DI can provide
● Run your own command line
● Cron Tasks, Setup, Etc.
Symfony Demo
The Framework in action…
Questions / Details
(Psst... There's more slides)
The Symfony2 Controller
use Symfony\Component\HttpFoundation\Response;
function helloAction(){ return new Response('Hello world!');}
● As a basic callable
The Symfony2 Controller
namespace Acme\HelloBundle\Controller;use Symfony\Component\HttpFoundation\Response;
class HelloController{ public function indexAction() { return new Response('Hello World!'); }}
● As a class method
The Symfony2 Controller
namespace Acme\HelloBundle\Controller;use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
class HelloController{ public function indexAction(Request $request) {
if ($request->isXmlHttpRequest()) { ... } }}
● Getting the Request object
The Symfony2 Controller
namespace Acme\HelloBundle\Controller;use Symfony\Component\HttpFoundation\Response;
class HelloController{ public function indexAction($name) { return new Response('Hello ' . $name . '!'); }}
● Parameters can be passed in from router
The Symfony2 Router
● Routes defined as a Yaml config
● app/config/routing.yml
hello_world: path: /hello/{name} defaults:{ _controller: AcmeHelloBundle:Hello:index }
The Symfony2 Router
● Routes can be defined with annotations
namespace Acme\HelloBundle\Controller;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class HelloController { /** * @Route("/hello/{name}") */ public function indexAction($name) { ... }}
The Symfony2 Router
● Routes follow HTTP GET/POST/PUT/DELETE specnamespace Acme\HelloBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
class HelloController { /** * @Route("/hello/{name}") * @Method("GET") */ public function indexAction($name) { ... }}
The Symfony2 View
Twig basics
● {{ ... }} Prints a variable or the result of an expression
● {% ... %} Controls the logic of the template;
The Symfony2 View
Properties, methods are accessed through dot notation{% for post in blogPosts %}<li> <ul> <a href="{{post.getLink()}}">{{post.title}}</a> </ul>
</li>{% endfor %}
The Symfony2 View
Twig views are extendable
● Parent template
● Broken into Blocks
● Blocks are extended or overridden by views
The Symfony2 View
A Base Template
<html> <head> <title> {% block title %}Example.com{% endblock %} </title> </head> <body> {% block body %}{% endblock %} </body></html>
The Symfony2 View
Blocks can be extended, or overridden
{% extends "::base.html.twig" %}
{% block title %}
Welcome! {{ parent() }}
{% endblock %}
{% block body %}
Hello World!
{% endblock %}
DependencyInjection
What is dependency injection?
● Inject needed objects into classes
o Constructor, setters, properties
● Used to decouple classes
● Use Common interfaces instead of concretes
DependencyInjection
The problem:public class User { private $session; public function __construct() { $this->session = new Session('SESSION_ID'); } ....}
DependencyInjection
Dependency Injection solutionpublic class User { private $session; public function __construct(SessionInterface $session) { $this->session = $session; } ....}
DependencyInjection
Configuring Servicesparameters: api.class: Acme\HelloBundle\Api curl.class: Acme\HelloBundle\Wrapper\Curlservices: api: class: "%api.class%" arguments: [@curl, "%api.key%"] curl: class: "%curl.class%" arguments: []
Doctrine Entities/** * @ORM\Table(name="widget") */class Widget{ /** * @var string $body * @ORM\Column(name="body", type="text", nullable=true) */ private $body; public function getBody() { ... } public function setBody($body) { ... }}
Table name
Doctrine Entities/** * @ORM\Table(name="widget") */class Widget{ /** * @var string $body * @ORM\Column(name="body", type="text", nullable=true) */ private $body; public function getBody() { ... } public function setBody($body) { ... }}
Column name
Doctrine Entities/** * @ORM\Table(name="widget") */class Widget{ /** * @var string $body * @ORM\Column(name="body", type="text", nullable=true) */ private $body;
public function getBody() { ... } public function setBody($body) { ... }}
Column type / attributes
Using Doctrine
● Data is accessed through the EntityManager
o Each table is a "Repository"
Doctrine Entitiesclass AcmeClass{ public function doSomething() { $repo = $this->doctrine->getRepository('AcmeBundle:Widgets'); $widget = $repo->findById(5); }}
Doctrine Entitiesclass AcmeClass{ public function doSomething() { $repo = $this->doctrine->getRepository('AcmeBundle:Widgets'); $widget = $repo->findById(5); }} Bundle
name
Doctrine Entitiesclass AcmeClass{ public function doSomething() { $repo = $this->doctrine->getRepository('AcmeBundle:Widgets'); $widget = $repo->findById(5); }} Entity name
Using Doctrine
● Repositories can be explicitly defined for an entity
o (Similar to Zend's Db_Table object)
Doctrine Entities/** * @ORM\Table(name="widget") */class Widget{ /** * @var string $body * @ORM\Column(name="body", type="text", nullable=true) */ private $body; public function getBody() { ... } public function setBody($body) { ... }}
Doctrine Entities/** * @ORM\Table(name="widget") * @ORM\Entity( * repositoryClass="Acme\HelloBundle\Repository\WidgetRepository" * ) */class Widget{ /** * @var string $body * @ORM\Column(name="body", type="text", nullable=true) */ private $body; public function getBody() { ... } public function setBody($body) { ... }}
Repository
Doctrine Repositoriesuse Doctrine\ORM\EntityRepository;
class WidgetRepository extends EntityRepository{ public function findWidgetsByName($string) { ... }}
Using Doctrine
Doctrine provides two ways to access data
● Query Builder
● DQL (Doctrine Query Language)
Using DQL
● DQL is structured like SQL
● Aimed at Entities, not at tables
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Entity Manager
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Bundle Name
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Entity Class
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Placeholder
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Placeholder Value
Using DQLclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $query = $this->entityManager->createQuery( "SELECT w FROM AcmeBlogBundle:Widgets w JOIN w.tag t WHERE t.title = :tag ORDER BY w.created DESC" ); $query->setParameter('tag', $tag); $results = $query->getResults();
return $results; }}
Result Collection
Using QueryBuilderclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $qb = $this->entityManager->createQueryBuilder(); $qb->select('w') ->from('AcmeBlogBundle:Widgets', 'w') ->innerJoin('w.tags', 't') ->where('t.title = :tag') ->setParameter('tag', $tag) ->orderBy('w.created'); $results = $qb->getQuery()->getResults();
return $results; }}
Bundle name
Using QueryBuilderclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $qb = $this->entityManager->createQueryBuilder(); $qb->select('w') ->from('AcmeBlogBundle:Widgets', 'w') ->innerJoin('w.tags', 't') ->where('t.title = :tag') ->setParameter('tag', $tag) ->orderBy('w.created'); $results = $qb->getQuery()->getResults();
return $results; }}
Entity name
Using QueryBuilderclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $qb = $this->entityManager->createQueryBuilder(); $qb->select('w') ->from('AcmeBlogBundle:Widgets', 'w') ->innerJoin('w.tags', 't') ->where('t.title = :tag') ->setParameter('tag', $tag) ->orderBy('w.created'); $results = $qb->getQuery()->getResults();
return $results; }}
Placeholder
Using QueryBuilderclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $qb = $this->entityManager->createQueryBuilder(); $qb->select('w') ->from('AcmeBlogBundle:Widgets', 'w') ->innerJoin('w.tags', 't') ->where('t.title = :tag') ->setParameter('tag', $tag) ->orderBy('w.created'); $results = $qb->getQuery()->getResults();
return $results; }}
Placeholder Value
Using QueryBuilderclass AcmeService { private $entityManager; public fucntion __construct(EntityManager $em) {...} public getWidgetsByTag($tag) { $qb = $this->entityManager->createQueryBuilder(); $qb->select('w') ->from('AcmeBlogBundle:Widgets', 'w') ->innerJoin('w.tags', 't') ->where('t.title = :tag') ->setParameter('tag', $tag) ->orderBy('w.created'); $results = $qb->getQuery()->getResults();
return $results; }}
Result Collection
Security Component
security: firewalls: secured_area: pattern: ^/ anonymous: ~ form_login: login_path: login check_path: login_check
Security Component
security: firewalls: secured_area: pattern: ^/ anonymous: ~ form_login: login_path: login check_path: login_check
Secure everything
Security Component
security: firewalls: secured_area: pattern: ^/foo anonymous: ~ form_login: login_path: login check_path: login_check
Secure things in:
/foo
Security Component
security: firewalls: secured_area: pattern: ^/foo anonymous: ~ form_login: login_path: login check_path: login_check
Allow anonymous
Security Component
security: firewalls: secured_area: pattern: ^/foo anonymous: ~ form_login: login_path: login check_path: login_check
login route
Security Component
security: firewalls: secured_area: pattern: ^/foo anonymous: ~ form_login: login_path: login check_path: login_check
login check route
Security Component
Oops! I accidentally the whole security, what should I do?
● Define Routes for login_path and check_path
● Make sure the login page isn't behind a secured firewall or access control
● Make sure check path is behind a firewall
● Multiple firewalls do not share security contexts!
Credits● Symfony Documentation
o Symfony.com/doc
● Fabien Potenciero What is symfonyo http://fabien.potencier.org/article/49/what-is-symfony2
● Symfony Live Presentationso Bernhard Schussek (Symfony Berlin / forms)
Credits
● Nerdery Symfony2 Fork
o github.com/TheNerdery/symfony-standard