Symfony2
Powerful and flexible web application framework in state of the art PHP
SunshinePHP, Feb 7th 2013 © David Buchmann, Liip AG
About David Buchmann
Twitter: @dbu
David is a senior developper at Liip SA, specializing in Symfony2. He happens to also be a certified Scrum Master and sometimes enjoys doing the scrum master or product owner role for a project.
Liip is doing custom web development with PHP in Switzerland.
Symfony2
- Modular framework
- Built from cleanly separated components (around 20 by now)
- Bundles and Dependency Injection
Bundles
Reusable code pieces with Symfony2 integration
- Provide services to Symfony2
- Provide a configuration entry point
- Directory layout convention
- Ideally just ties a stand-alone library into Symfony
Outline
- Start a project
- Create your first bundle
- Controler, View, Model
- Doctrine and Forms
- Some more background on Symfony2
Fr 13:30-14:30
Jordi Boggiano
Composer: Dependency management reloaded
- Declare dependencies of your application
- Libraries declare their dependencies
- Resolves the tree and downloads all needed libraries
- Per project, not global in your system
- Integrates autoloading
- Simple to use, simple to publish libraries
Setting up a project
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar create-project symfony/framework-standard-edition my-project 2.1.x-dev
Edit composer.json extra section:
"extra": {
"symfony-app-dir": "app",
"symfony-web-dir": "web",
"symfony-assets-install": "symlink"
}
Configure your web server to point to web subfolder of my-project
Directory Layout
- app/ application configuration, bootstrap and overwritten files
- src/ code specific to this application, organized as bundles
- vendor/ all third party code, managed by composer
- web/ web root, application entry point and static files
Create your first bundle
$ php app/console generate:bundle --namespace=Sunshine/HelloBundle --format=yml
# simply accept all defaults, they are fine
This creates code and adds our bundle to the Kernel
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Sunshine\HelloBundle\SunshineHelloBundle(),
);
// ...
return $bundles;
}
And also links the routing file in app/config/routing.yml
Bundle Directory Layout
- Controller/ controller classes like HelloController.php
- DependencyInjection/ for managing bundle configuration (advanced concept)
- Resources/config/ configuration files for this bundle, e.g. routing
- Resources/views/ template files organized by controller name (e.g. Hello/index.html.twig)
- Resources/public/ static files to expose through the web/ directory (images, stylesheets, javascript). See assets:install
- Tests/ phpunit tests of this bundle.
The MVC of Symfony
- Model: i.e. Doctrine ORM|ODM|PHPCR-ODM, Propel
- View: i.e. Twig (or PHP, ...)
- Control: HttpKernel, Routing, Controller classes
Control
Routing: Map request to controller
# src/Sunshine/HelloBundle/Resources/config/routing.yml
sunshine_hello_homepage:
pattern: /sunshine/{name}
defaults: { _controller: SunshineHelloBundle:Hello:index }
Symfony will:
- Match on URL /sunshine/ followed by anything except '/'
- Instantiate class Sunshine\HelloBundle\Controller\HelloController
- And call indexAction on it with $name as parameter
Controller
namespace Sunshine\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response("Hello $name");
}
}
View
Fr 11:30-12:30
Javier Equiluz
Twig templating
<!DOCTYPE html>
<html>
<head>
<title>Welcome to SunshinePHP!</title>
</head>
<body>
<h1>Hello: {{ name }}</h1>
</body>
</html>
Use template in Controller
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return $this->render(
'SunshineHelloBundle:Hello:index.html.twig',
array('name' => $name)
);
}
}
Model
Domain object
<?php
namespace Sunshine\HelloBundle\Entity;
class Page
{
private $mainText;
public function __construct($text)
{
$this->mainText = $text;
}
public function getMainText()
{
return $this->mainText;
}
public function setMainText($text)
{
$this->mainText = $text;
}
}
Use it
Controller
$page = new \Sunshine\HelloBundle\Entity\Page('test');
return $this->render(
'SunshineHelloBundle:Default:index.html.twig',
array('name' => $name, 'page' => $page)
);
Template
<p>{{ page.mainText }}</p>
- If object, getText() or public property text
- If array check if field 'text'
Doctrine ORM
- Object-Relational mapper
- Any PHP objects, not active record
- Map with annotations, XML or yaml files
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Page
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="main_text", type="string")
*/
private $mainText;
...
Set up database
- configure in app/config/config.yml + app/config/parameters.yml
- pdo_sqlite with additional path option set to '%kernel.root_dir%/app.sqlite'
- app/console doctrine:database:create
- sqlite does not have ALTER TABLE so database:drop and database:create after schema change
- Note: you want to use pdo_psql or pdo_mysql in real live
Console commands to manage database
$ app/console doctrine:schema:update --dump-sql
CREATE TABLE Page (id INTEGER NOT NULL, main_text VARCHAR(255) NOT NULL, PRIMARY KEY(id))
$ app/console doctrine:schema:update --force
Updating database schema...
Database schema updated successfully! "1" queries were executed
$ app/console doctrine:schema:drop --dump-sql
DROP TABLE Page
# --force would actually drop it
Insert Doctrine Objects
Controller
public function createAction()
{
$page = new Page('SunshinePHP in sunny florida');
$em = $this->getDoctrine()->getManager();
$em->persist($page);
$em->flush();
return new Response('Created page id '.$page->getId());
}
Route
sunshine_hello_create:
pattern: /sunshine-create
defaults: { _controller: SunshineHelloBundle:Default:create }
Read Doctrine Objects
Controller
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('SunshineHelloBundle:Page')
$page = $repository->find($id);
if (! $page) {
throw new NotFoundHttpException("no page $id");
}
// render as before
}
Route
sunshine_hello_show:
pattern: /sunshine-show/{id}
defaults: { _controller: SunshineHelloBundle:Default:show }
Doctrine shell commands
- doctrine:mapping:info - informations about the entities you defined
- doctrine:generate:entities - generate classes from xml/yml mapping information
- doctrine:generate:entity - interactive shell to define entity
- doctrine:mapping:import - generate entities by inspecting an existing database
- doctrine:migrations:status|diff|migrate - manage database changes in change scripts
Sa 13:30-14:30
Bernhard Schussek
Symfony2 form component
- Convert between object graphs and HTML forms
- Very powerful
- But also fairly challenging to get into for complex use cases
Form Controller
public function createAction(Request $request)
{
$page = new Page('');
$form = $this->createFormBuilder($page)
->add('mainText', 'textarea')
->getForm();
return $this->render(
'SunshineHelloBundle:Default:form.html.twig',
array('form' => $form->createView()));
}
Template
<form action="{{ path('sunshine_hello_create') }}"
method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
Handle Form Submit
// create form, then
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($page);
$em->flush();
$showUrl = $this->generateUrl(
'sunshine_hello_show',
array('id' => $page->getId())
);
return $this->redirect($showUrl);
}
}
Use an existing bundle
Use Bundles in your projects
- PHP has lots of cool libraries
- Bundles integrate them into Symfony2
- Fight the NIH syndrome
- Each bundle you can use is one less you need to write and maintain
- knpbundles.com
- packagist.org
Integrate KnpMarkdownBundle
php composer.phar require "knplabs/knp-markdown-bundle:1.2.*@dev"
Add to app/AppKernel.php
$bundles = array(
// ...
new Knp\Bundle\MarkdownBundle\KnpMarkdownBundle(),
);
Use it in template
{{ page.mainText|markdown }}
Working with Symfony2
Configuration and Environments
- Configuration from xml or yml files in app/config/
- Environment set from the entry points in web/
- load config_.yml
- Import other configuration files from there
- Most configuration shared in config.yml
- Security configuration for access control
Using git
- Ignore the vendor/ directory
- But track the composer.lock file (and composer.json of course)
- The standard edition provides a reasonable .gitignore file
- Provide parameters.yml.dist file but have parameters.yml ignored
Configure filesystem permissions
If you can do chmod +a:
$ rm -rf app/cache/*
$ rm -rf app/logs/*
$ sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
$ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
Otherwise set up ACL and:
$ sudo setfacl -R -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs
$ sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs
Sa 10:15-11:15
Hugo Hamon
Lots of interesting components
- ClassLoader: Autoload PSR-0 classes
- Console: Build console commands
- Form: Build and handle web forms
- HttpFoundation: HTTP in object oriented
- DomCrawler and CssSelector: Web scraper and functional testing
- EventDispatcher: Register to events and define your own event channels
- Process: Execute commands in separate unix processes
- Validator: Validation of object fields
- Yaml: parse .yml files
Dependeny Injection
Dependeny Injection
class MyClass
{
public function do()
{
global $variable;
echo $variable;
}
}
NO!
Dependeny Injection
class MyClass
{
public function do()
{
echo Registry::instance()->get('variable');
}
}
Meh. A bit better but not good.
Dependeny Injection
class MyClass
{
private $variable;
public function __construct($variable)
{
$this->variable = $variable;
}
public function do()
{
echo $this->variable;
}
}
This is dependency injection!
Dependeny Injection
- Push instead of pull
- Dependencies are explicit
- Code is more reusable
- Code is better testable
Symfony2 Dependeny Injection
- Service is lazy loaded class instance
- Define what other services need to be injected
- Service container only instantiates when actually needed
- Application logic in services, inject into controller
Thank you!
Questions / Input / Feedback ?
Related talks
Friday
- Next up: Twig Tips and Tricks, Javier Equiluz
- 13:30: Dependency Management with Composer, Jordi Boggiano
- 14:45: Building OpenSky with Symfony2, Jonathan Wage
- 16:00: Silex, the Microframework, Ben Longden (components)
Saturday
- 10:15: Symfony Components in Legacy Code, Hugo Hamon
- 10:15: Assetic, Varnish and ESI, David Buchmann
- 11:30: Dream, Experiment, Create, and Share, Fabien Potencier (creator of Symfony2)
- 13:30: Handling Forms with Symfony2, Bernhard Schussek
- 14:45: How Kris Writes Symfony Apps, Kris Wallsmith
- 16:00: Symfony2 + EmberJS for fun and profit, Dustin Whittle
- 16:00: Symfony and Javascript, Nacho Martin