fluent development with flow3 1.0
DESCRIPTION
FLOW3 is an application framework which will change the way you code PHP. It aims to back up developers with security and infrastructure while they focus on the application logic. FLOW3 is one of the first application frameworks to choose Domain-Driven Design as its major underlying concept. This approach makes FLOW3 easy to learn and at the same time clean and flexible for even complex projects. Built with PHP 5.3 in mind from the beginning, it features namespaces and has an emphasis on clean, object-oriented code.Thanks to its Doctrine 2 integration, FLOW3 gives you access to a wide range of databases while letting you forget the fact that you're using a database at all (think objects, not tables). FLOW3's unique way of supporting Dependency Injection (no configuration necessary) lets you truly enjoy creating a stable and easy-to-test application architecture. Being the only Aspect-Oriented Programming capable PHP framework, FLOW3 allows you to cleanly separate cross cutting concerns like security from your main application logic.This tutorial provides a comprehensive overview of the main features of FLOW3 and how you can get started with your first app.TRANSCRIPT
San Francisco, USA
Fluent Development with FLOW3
Karsten Dambekalns & Robert Lemke
San Francisco, USA
co-lead of TYPO3 5.0 and FLOW3
34 years old
lives in Lübeck, Germany
1 wife, 3 sons, 1 espresso machine
likes canoeing
Karsten Dambekalns
San Francisco, USA
chief "architect" of TYPO3 5.0 and FLOW3
co-founder of the TYPO3 Association
35 years old
lives in Lübeck, Germany
1 wife, 1 daughter, 1 espresso machine
likes drumming
Robert Lemke
San Francisco, USA
At a Glance
FLOW3 is a web application framework
• brings PHP development to a new level
• made for PHP 5.3, full namespaces support
• modular, extensible, package based
• free & Open Source (LGPL v3)
• backed by one of the largest Open Source projects
with 6000+ contributors
San Francisco, USA
Foundation for the Next Generation
TYPO3 5.0 is the all-new Enterprise CMS
• content repository, workspaces, versions, i18n, ExtJS based UI ...
• powered by FLOW3
• compatible code base
• use TYPO3 features in FLOW3 standalone apps as you like
San Francisco, USA
Work in Progress
WARNING
The current documentation of FLOW3 does not cover the version in our Git master – a lot has changed since the last alpha release!
We are currently updating the manuals and tutorials for the 1.0 beta release though.
San Francisco, USA
Check Out from Git – Stable State
$ git clone --recursive git://git.typo3.org/FLOW3/Distributions/Base.git .Cloning into ....remote: Counting objects: 3837, done.remote: Compressing objects: 100% (2023/2023), done.remote: Total 3837 (delta 2007), reused 2721 (delta 1465)Receiving objects: 100% (3837/3837), 3.49 MiB | 28 KiB/s, done.Resolving deltas: 100% (2007/2007), done.
San Francisco, USA
Set File Permissions
$ ./Packages/Framework/FLOW3/Scripts/setfilepermissions.sh robert _www _wwwFLOW3 File Permission Script
Checking permissions from here upwards ... (if a password prompt appears it's from sudo)Password:
Making sure Data and Web/_Resources exist ...Setting file permissions, this might take a minute ...
$
San Francisco, USA
Set Up Database
Configuration/Settings.yaml
# ## Global Settings ## #
FLOW3: persistence: backendOptions: driver: 'pdo_mysql' dbname: 'blog' user: 'bloguser' password: 'blogpassword' host: '127.0.0.1' path: '127.0.0.1' port: 3306 doctrine: dbal: sessionInitialization: 'SET NAMES utf8 COLLATE utf8_unicode_ci'
San Francisco, USA
Set Up Virtual Host
Apache Virtual Host
<VirtualHost *:80> DocumentRoot /opt/local/apache2/htdocs/Talks/FLOW3/Web/ ServerName dev.flow3.rob SetEnv FLOW3_CONTEXT Development</VirtualHost>
<VirtualHost *:80> DocumentRoot /opt/local/apache2/htdocs/Talks/FLOW3/Web/ ServerName flow3.rob SetEnv FLOW3_CONTEXT Production</VirtualHost>
San Francisco, USA
Final Check
San Francisco, USA
Update from Git to Latest State
$ git submodule foreach "git checkout master"
-✂-----✂-----✂-----✂-----✂-----✂-----✂-----✂-----✂-----✂-----✂-----✂-
$ git submodule foreach "git pull --rebase"
Entering 'Build/Common'First, rewinding head to replay your work on top of it...Fast-forwarded master to 6f27f1784240b414e966ce0e5a12e23cb2f7ab02.Entering 'Packages/Application/TYPO3'First, rewinding head to replay your work on top of it...Fast-forwarded master to 5187430ee44d579ae2bac825e2a069c4cd3f38a4.Entering 'Packages/Application/TYPO3CR'First, rewinding head to replay your work on top of it...Fast-forwarded master to b1f5331aa51d390fa3d973404f31b9fd773f7059.Entering 'Packages/Application/Twitter'Current branch master is up to date.…
San Francisco, USA
Hello World!
Package.php
<?phpnamespace F3\Demo;
use \F3\FLOW3\Package\Package as BasePackage;
class Package extends BasePackage {}
$ ./flow3_dev flow3:package:create --package-key Demo
$ ./flow3_dev flow3:package:create Demo
Soon:
Today:
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 11
Live Hacking
San Francisco, USA
Hello World!
<?phpnamespace F3\Demo\Controller;
use \F3\FLOW3\MVC\Controller\ActionController;
class StandardController extends ActionController { /** * @param string $name * @return string */ public function indexAction($name) { return "Hello $name!"; }}
?>
StandardController.php
San Francisco, USA
Hello World!
http://dev.flow3.rob/demo/standard/index?name=Robert
Hello Robert!
San Francisco, USA
Tackling the Heart of Software Development
Domain-Driven DesignA methodology which ...
• results in rich domain models
• provides a common language across the project team
• simplify the design of complex applications
FLOW3 is the first PHP framework tailored to Domain-Driven Design
/** * Paper submitted by a speaker * * @scope prototype * @entity */class Paper {
/** * @var Participant */ protected $author;
/** * @var string */ protected $title;
/** * @var string */ protected $shortAbstract;
/** * @var string */ protected $abstract;
/** * @var \SplObjectStorage */ protected $materials;
/** * @var \F3\Conference\Domain\Model\SessionType * @validate NotEmpty */ protected $proposedSessionType;
/** * Constructs a new Paper * * @author Robert Lemke <[email protected]> */ public function __construct() { $this->materials = new \SplObjectStorage; }
/** * Sets the author of this paper * * @param \F3\Conference\Domain\Model\Participant $author * @return void * @author Robert Lemke <[email protected]> */ public function setAuthor(\F3\Conference\Domain\Model\Participant $author) { $this->author = $author; }
/** * Getter for the author of this paper * * @return \F3\Conference\Domain\Model\Participant * @author Robert Lemke <[email protected]> */ public function getAuthor() { return $this->author; }
/** * Setter for title * * @param string $title The title of this paper * @return void * @author Robert Lemke <[email protected]> */ public function setTitle($title) { $this->title = $title; }
/** * Getter for title * * @return string The title of this paper * @author Robert Lemke <[email protected]> */ public function getTitle() { return $this->title; }
/** * Setter for the short abstract * * @param string $shortAbstract The short abstract for this paper * @return void * @author Robert Lemke <[email protected]> */ public function setShortAbstract($shortAbstract) { $this->shortAbstract = $shortAbstract; }
/** * Getter for the short abstract * * @return string The short abstract * @author Robert Lemke <[email protected]> */ public function getShortAbstract() { return $this->shortAbstract; }
/** * Setter for abstract * * @param string $abstract The abstract of this paper * @return void * @author Robert Lemke <[email protected]> */ public function setAbstract($abstract) { $this->abstract = $abstract; }
/** * Getter for abstract * * @return string The abstract * @author Robert Lemke <[email protected]> */ public function getAbstract() { return $this->abstract; }
/** * Returns the materials attached to this paper * * @return \SplObjectStorage The materials * @author Robert Lemke <[email protected]> */ public function getMaterials() { return $this->materials; }
/** * Setter for the proposed session type * * @param \F3\Conference\Domain\Model\SessionType $proposedSessionType The proposed session type * @return void * @author Robert Lemke <[email protected]> */ public function setProposedSessionType(\F3\Conference\Domain\Model\SessionType $proposedSessionType) { $this->proposedSessionType = $proposedSessionType; }
/** * Getter for the proposed session type * * @return \F3\Conference\Domain\Model\SessionType The proposed session type * @author Robert Lemke <[email protected]> */ public function getProposedSessionType() { return $this->proposedSessionType; }}?>
San Francisco, USA
Domain-Driven Design
Domain activity or business of the user
Domain-Driven Design is about
• focussing on the domain and domain logic
• accurately mapping the concepts to software
• forming a ubiquitous language among the project members
San Francisco, USA
Domain-Driven Design
Ubiquitous Language
• important prerequisite for successful collaboration
• use the same words for
• discussion
• modeling
• development
• documentation
San Francisco, USA
Domain: Conference
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 12
Extreme Modeling
San Francisco, USA
Example Model: Paper
/** * A Paper * * @scope prototype * @entity */class Paper {
/** * @var \F3\Conference\Domain\Model\Account\Participant * @ManyToOne(cascade={"persist"}) * @validate NotEmpty */ protected $author;
/** * @var string * @validate StringLength(minimum = 1, maximum = 50) */ protected $title;
San Francisco, USA
Example Model: Paper
/** * @var string * @Column(type="text") * @validate StringLength(minimum = 30, maximum = 150) */ protected $shortAbstract;
/** * @var string * @Column(type="text") * @validate StringLength(minimum = 100, maximum = 1000) */ protected $abstract;
/** * @var \F3\Conference\Domain\Model\Conference\SessionType * @ManyToOne(cascade={"persist"}) * @validate NotEmpty */ protected $proposedSessionType;
San Francisco, USA
Example Model: Paper
/** * @var \F3\Conference\Domain\Model\Conference\Track * @ManyToOne(cascade={"persist"}) * @validate NotEmpty */ protected $proposedTrack;
/** * @var \Doctrine\Common\Collections\ArrayCollection<\F3\Conference\Domain\Model\Comment> * @ManyToMany(cascade={"persist", "remove"}) */ protected $comments;
/** * @var string */ protected $slideShareUrl;
/** * @var string */ protected $tags;
San Francisco, USA
Example Model: Paper
/** * @var string * @Column(type="text", nullable=true) */ protected $links;
/** * @var \Doctrine\Common\Collections\ArrayCollection<\F3\Conference\Domain\Model\Account\Participant> * @ManyToMany(cascade={"all"}) */ protected $speakers;
/** * one of the STATUS_* constants * @var string */ protected $status = self::STATUS_DRAFT;
San Francisco, USA
Example Model: Paper
/** * STATUS_* constants */ const STATUS_DRAFT = 'draft'; const STATUS_SUBMITTED = 'submitted'; const STATUS_REJECTED = 'rejected'; const STATUS_ACCEPTED = 'accepted'; const STATUS_SCHEDULED = 'scheduled';
public function __construct() { $this->comments = new \Doctrine\Common\Collections\ArrayCollection(); $this->speakers = new \Doctrine\Common\Collections\ArrayCollection(); }
/** * @param \F3\Conference\Domain\Model\Account\Participant $author * @return void */ public function setAuthor(\F3\Conference\Domain\Model\Account\Participant $author) { $this->author = $author; }
/** * @return \F3\Conference\Domain\Model\Account\Participant */ public function getAuthor() {
San Francisco, USA
Domain-Driven Design
San Francisco, USA
Persistence
Object Persistence in the Flow
• based on Doctrine 2
• seamless integration into FLOW3
• provides all the great Doctrine 2 features
• uses UUIDs
• low level persistence API:
• allows for own, custom persistence backends (instead of Doctrine 2)
• CouchDB is supported natively
San Francisco, USA
Basic Object Persistence
// Create a new customer and persist it: $customer = new Customer("Robert"); $this->customerRepository->add($customer);
// Find an existing customer: $otherCustomer = $this->customerRepository->findByFirstName("Karsten"); // and delete it: $this->customerRepository->remove($otherCustomer);
San Francisco, USA
Advanced Queries
/** * Finds most recent posts excluding the given post * * @param \F3\Blog\Domain\Model\Post $post Post to exclude from result * @param integer $limit The number of posts to return at max * @return array All posts of the $post's blog except for $post */ public function findRecentExceptThis(\F3\Blog\Domain\Model\Post $post, $limit = 20) { $query = $this->createQuery(); $posts = $query->matching($query->equals('blog', $post->getBlog())) ->setOrderings(array('date' => \F3\FLOW3\Persistence\QueryInterface::ORDER_DESCENDING)) ->setLimit($limit) ->execute() ->toArray(); unset($posts[array_search($post, $posts)]); return $posts;
// this is an alternative way of doing this when extending the Doctrine 2 // specific repository and using DQL. return $this->entityManager ->createQuery('SELECT p FROM \F3\Blog\Domain\Model\Post p WHERE p.blog = :blog' .
'AND NOT p = :excludedPost ORDER BY p.date DESC') ->setMaxResults($limit) ->execute(array('blog' => $post->getBlog(), 'excludedPost' => $post)); }
PostRepository.php
San Francisco, USA
Purely Doctrine 2
<?phpnamespace My\Example;
/** * @Entity(repositoryClass="BugRepository") */class Bug {
/** * @var integer * @Id * @Column(type="integer") * @GeneratedValue */ public $id;
/** * @var string * @Column(type="string") */ public $description;
/** * @var \DateTime * @Column(type="datetime") */ public $created;
/** * @var User * @ManyToOne(targetEntity="User", inversedBy="assignedBugs") */ private $engineer;
/** * @var \Doctrine\Common\Collections\ArrayCollection<Product> * @ManyToMany(targetEntity="Product") */ private $products;}
?>
San Francisco, USA
Doctrine 2 in FLOW3
<?phpnamespace My\Example;
/** * @Entity(repositoryClass="BugRepository") */class Bug {
/** * @var integer * @Id * @Column(type="integer") * @GeneratedValue */ public $id;
/** * @var string * @Column(type="string") */ public $description;
/** * @var \DateTime * @Column(type="datetime") */ public $created;
/** * @var User * @ManyToOne(targetEntity="User", inversedBy="assignedBugs") */ private $engineer;
/** * @var \Doctrine\Common\Collections\ArrayCollection<Product> * @ManyToMany(targetEntity="Product") */ private $products;}
?>
San Francisco, USA
Purely Doctrine 2
/** * @var \DateTime * @Column(type="datetime") */ public $created;
/** * @var User * @ManyToOne(targetEntity="User", inversedBy="assignedBugs") */ private $engineer;
/** * @var \Doctrine\Common\Collections\ArrayCollection<Product> * @ManyToMany(targetEntity="Product") */ private $products;}
?>
San Francisco, USA
Doctrine 2 in FLOW3
/** * @var \DateTime * @Column(type="datetime") */ public $created;
/** * @var User * @ManyToOne(targetEntity="User", inversedBy="assignedBugs") */ private $engineer;
/** * @var \Doctrine\Common\Collections\ArrayCollection<Product> * @ManyToMany(targetEntity="Product") */ private $products;}
?>
San Francisco, USA
Object Management
Dependency Injection
• a class doesn't create or retrieve the instance of another class but get's it injected
• fosters loosely-coupling and high cohesion
‣ more stable, reusable code
San Francisco, USA
Object Management
FLOW3's take on Dependency Injection
• one of the first PHP implementations(started in 2006, improved ever since)
• object management for the whole lifecycle of all objects
• no unnecessary configuration if information can be gathered automatically (autowiring)
• intuitive use and no bad magical surprises
• fast! (like hardcoded or faster)
San Francisco, USA
<?phpnamespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\RedirectResponse;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;use Acme\DemoBundle\GreeterService;
class DemoController extends Controller { /** * @var \Acme\DemoBundle\GreeterService */ protected $greeterService;
/** * @param \Acme\DemoBundle\GreeterService */ public function __construct($greeterService = NULL) { $this->greeterService = $greeterService; } /** * @Route("/hello/{name}", name="_demo_hello") */ public function helloAction($name) { return new Response('Hello ' . $name, 200, array('Content-Type' => 'text/plain')); }}
Constructor Injection: Symfony 2Warning: might contain errors
(I'm no Symfony expert ...)
San Francisco, USA
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <service id="acme.demo.greeterservice" class="Acme\DemoBundle\GreeterService" public="false" /> <service id="acme.demo.democontroller" class="Acme\DemoBundle\Controller\DemoController"> <argument type="service" id="acme.demo.greeterservice" /> </service> </services></container>
Constructor Injection: Symfony 2Warning: might contain errors
(I'm no Symfony expert ...)
San Francisco, USA
<?php namespace F3\Demo\Controller;
use F3\FLOW3\MVC\Controller\ActionController;use F3\Demo\Service\GreeterService;
class DemoController extends ActionController { /** * @var \F3\Demo\Service\GreeterService */ protected $greeterService;
/** * @param \F3\Demo\Service\GreeterService */ public function __construct(\F3\Demo\Service\GreeterService $greeterService) { $this->greeterService = $greeterService; } /** * @param string $name */ public function helloAction($name) { return 'Hello ' . $name; }}
Constructor Injection
San Francisco, USA
Constructor Injection
San Francisco, USA
<?php namespace F3\Demo\Controller;
use F3\FLOW3\MVC\Controller\ActionController;use F3\Demo\Service\GreeterService;
class DemoController extends ActionController { /** * @var \F3\Demo\Service\GreeterService */ protected $greeterService;
/** * @param \F3\Demo\Service\GreeterService */ public function injectGreeterService(\F3\Demo\Service\GreeterService $greeterService) { $this->greeterService = $greeterService; } /** * @param string $name */ public function helloAction($name) { return 'Hello ' . $name; }}
Setter Injection
San Francisco, USA
<?php namespace F3\Demo\Controller;
use F3\FLOW3\MVC\Controller\ActionController;use F3\Demo\Service\GreeterService;
class DemoController extends ActionController { /** * @var \F3\Demo\Service\GreeterService * @inject */ protected $greeterService; /** * @param string $name */ public function helloAction($name) { return 'Hello ' . $name; }}
Property Injection
San Francisco, USA
F3\FLOW3\Security\Cryptography\RsaWalletServiceInterface: className: F3\FLOW3\Security\Cryptography\RsaWalletServicePhp scope: singleton properties: keystoreCache: object: factoryObjectName: F3\FLOW3\Cache\CacheManager factoryMethodName: getCache arguments: 1: value: FLOW3_Security_Cryptography_RSAWallet
Objects.yaml
San Francisco, USA
Object Management
FLOW3's take on Dependency Injection
• one of the first PHP implementations(started in 2006, improved ever since)
• object management for the whole lifecycle of all objects
• no unnecessary configuration if information can be gatered automatically (autowiring)
• intuitive use and no bad magical surprises
• fast! (like hardcoded or faster)
San Francisco, USA
class Customer {
/** * @inject * @var CustomerNumberGenerator */ protected $customerNumberGenerator;
...}
$customer = new Customer();$customer->getCustomerNumber();
Object Management
San Francisco, USA
Object Management
<?phpdeclare(ENCODING = 'utf-8');namespace F3\Conference\Domain\Model\Conference;/** * Autogenerated Proxy Class * @scope prototype * @entity */class Paper extends Paper_Original implements \F3\FLOW3\Object\Proxy\ProxyInterface, \F3\FLOW3\Persistence\Aspect\PersistenceMagicInterface { /** * @var string * @Id * @Column(length="40") * introduced by F3\FLOW3\Persistence\Aspect\PersistenceMagicAspect */ protected $FLOW3_Persistence_Identifier = NULL; private $FLOW3_AOP_Proxy_targetMethodsAndGroupedAdvices = array(); private $FLOW3_AOP_Proxy_groupedAdviceChains = array(); private $FLOW3_AOP_Proxy_methodIsInAdviceMode = array();
/** * Autogenerated Proxy Method */ public function __construct() { $this->FLOW3_AOP_Proxy_buildMethodsAndAdvicesArray(); if (isset($this->FLOW3_AOP_Proxy_methodIsInAdviceMode['__construct'])) { parent::__construct(); } else {
FLOW3 creates proxy classesfor realizing DI and AOP magic
• new operator is supported
• proxy classes are created on the fly
• in production context all code is static
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 13
Birth of a Blog
San Francisco, USA
The Zen of Templating
FLOW3 comes with an elegant, flexible and secure templating engine: Fluid
• templates are valid HTML
• templates contain no PHP code
• object access, control structures, loops ...
• designer-friendly
• extensible (view helpers, widgets)
San Francisco, USA
Fluid
Example for assigning a string to a Fluid variable:
<!-- in the Fluid template: --> <head> <title>{title}</title> </head>
// in the action controller: $this->view->assign('title', 'Welcome to Fluid');
San Francisco, USA
Fluid
Variables can also be objects:
<!-- in the Fluid template: --> <div class="venue"> <p>Venue Street: {conference.venue.street}</p> </div>
// in the action controller: $this->view->assign('conference', $conference);
San Francisco, USA
Fluid
if-then-else:
<!-- in the Fluid template: --> <f:if condition="{post.comments}"> <f:then>There are some comments.</f:then> <f:else>There are no comments.</f:else> </f:if>
// in the action controller: $this->view->assign('post', $blogPost);
San Francisco, USA
Fluid
for-each:
<!-- in the Fluid template: --> <ul> <f:for each="{ages}" as="age" key="name"> <li>{name} is {age} year old.</li> </f:for> </ul>
// in the action controller: $this->view->assign('ages', array("Karsten" => 34, "Robert" => 35));
San Francisco, USA
Fluid
for-each:
<!-- in the Fluid template: --> <f:if condition="{post.comments}"> <ul> <f:for each="{post.comments}" as="comment" > <li>{post.title}</li> </f:for> </ul> </f:if>
// in the action controller: $this->view->assign('post', $blogPost);
San Francisco, USA
Fluid
View helpers – in this case the link.action view helper:
<!-- in the Fluid template: --> {namespace f=F3\Fluid\ViewHelpers}
<f:link.action action="delete" arguments="{post: post, really: 'yes'}"> Delete this post </f:link.action>
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 18
Fluent Fluid
San Francisco, USA
Forms
<?phpnamespace F3\Blog\Domain\Model;
/** * A blog post * * @scope prototype * @entity */class Post {
/** * @var string * @validate StringLength(minimum = 1, maximum = 100) */ protected $title;
/** * @var string * @validate StringLength(minimum = 1, maximum = 50) */ protected $author;
/** * @var string * @validate Html */ protected $content;
/** * @var F3\Blog\Domain\Model\Image */ protected $image;
San Francisco, USA
Forms
<h2>Create a new post</h2>
<f:form action="create" object="{newPost}" name="newPost" enctype="multipart/form-data"> <label for="title">Title</label><br /> <f:form.textbox property="title" id="title" /><br />
<label for="content">Content</label><br /> <f:form.textarea property="content" rows="5" cols="40" id="content" /><br />
<label for="image">Image resource</label><br /> <f:form.textbox property="image.title" value="My image title" /> <f:form.upload property="image.originalResource" /></f:form>
San Francisco, USA
Forms
<?phpnamespace F3\Blog\Controller;use \F3\FLOW3\MVC\Controller\ActionController;
class PostController extends ActionController {
/** * @inject * @var \F3\Blog\Domain\Repository\PostRepository */ protected $postRepository;
/** * Creates a new post * * @param \F3\Blog\Domain\Model\Post $newPostadded to the repository * @return void */ public function createAction(\F3\Blog\Domain\Model\Post $newPost) { $this->blog->addPost($newPost); $this->flashMessageContainer->add('Your new post was created.'); $this->redirect('index'); }
}?>
San Francisco, USA
Fluent Development with FLOW3 (PART TWO)
Karsten Dambekalns & Robert Lemke
San Francisco, USA
Validation
Validation is about different things
• incoming data needs to be validated for security reasons
• no evil markup in submitted content
• domain model integrity needs to be ensured
• an email needs to be (syntactically) valid
• credit card numbers should consist only of digits
San Francisco, USA
Validation
Validation in FLOW3
• you do not want to code checks into your controllers
• FLOW3 separates validation from your controller’s concerns
• no PHP code needed for validation
• declared through annotations
San Francisco, USA
Validation
Validation Models
• BasePropertiesrules defining the minimum requirements on individual properties of a model
• BaseModelrules or custom validators enforcing the minimum requirements on the combination of properties of a model
• Supplementalrules defining additional requirements on a model for a specific situation (e.g. a certain action method)
San Francisco, USA
Validation
Base Properties
• Validation rules defined directly at the properties
/** * @var string * @validate StringLength(minimum = 10, maximum = 100) */ protected $title;
/** * @var string * @validate StringLength(minimum = 1, maximum = 50) */ protected $author;
San Francisco, USA
Validation
Validators
• validators provided by FLOW3 can be used through their short name
• Count, Float, NotEmpty, RegularExpression, Uuid, DateTime, NumberRange, StringLength, Alphanumeric, Integer, Number, String, EmailAddress, Label, Raw, Text
• custom validators need to implement the ValidatorInterface
• use them by specifying the fully qualified class name
/** * @var \Dambekalns\Stuff\Domain\Model\Stuff * @validate \Dambekalns\Stuff\Domain\Validator\StuffValidator */ protected $stuff;
San Francisco, USA
Schema Management
Automatic database updates
• when first running FLOW3 the schema will be created
• when a model has changed, the schema is updated
• be careful with existing data, updates can be destructive
• for production you should manually manage schema changes
San Francisco, USA
Schema Management
Manual database updates
• for simple situations this can be good enough:
• useful when
• you need to use an existing database dump
• using SQLite, due to limited schema change functionality
$ ./flow3_dev flow3:doctrine:create
$ ./flow3_dev flow3:doctrine:update
San Francisco, USA
Schema Management
Doctrine 2 Migrations
• Migrations allow schema versioning and change deployment
• Migrations are the recommended way for DB updates
• Tools to create and deploy migrations are integrated with FLOW3
San Francisco, USA
Schema Management
Migrations Workflow
• use schema auto update in early development until your model is ready for a first “freeze”, then switch off schema auto update and drop your tables
• create migration diff and customize it
• migrate to create the tables
$ ./flow3_dev flow3:doctrine:migrationdiffGenerated new migration class to "…/Version20110608074324.php" from schema differences.$ vi …/Version20110608074324.php
$ ./flow3_dev flow3:doctrine:migrate
San Francisco, USA
Schema Management
Migrations Workflow
$ ./flow3_dev flow3:doctrine:migrationstatus
== Configuration >> Name: Doctrine Database Migrations >> Database Driver: pdo_mysql >> Database Name: blog >> Configuration Source: manually configured >> Version Table Name: flow3_doctrine_migrationstatus >> Migrations Namespace: F3\FLOW3\Persistence\Doctrine\Migrations >> Migrations Directory: /Users/karsten/Sites/blog/Configuration/Doctrine/Migrations >> Current Version: 2011-06-08 07:43:24 (20110608074324) >> Latest Version: 2011-06-08 07:43:24 (20110608074324) >> Executed Migrations: 1 >> Available Migrations: 1 >> New Migrations: 0
== Migration Versions >> 2011-06-08 07:43:24 (20110608074324) migrated
San Francisco, USA
Schema Management
Migrations Workflow
• rinse and repeat: from now on create a new migration whenever you changed your model classes
• generated migrations most probably need to be adjusted
• e.g. renaming a model means renaming a table, not dropping and creating
• data migration needs to be added
• remember: good migrations make your user’s day
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 18
Schematic Demo
San Francisco, USA
Command Line Support
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 18
Shebang!
San Francisco, USA
Command Line Support
<?phpnamespace F3\FLOW3\Command;
/** * Package command controller to handle packages from CLI (create/activate/deactivate packages) * * @scope singleton */class PackageCommandController extends \F3\FLOW3\MVC\Controller\CommandController {
/** * @inject * @var F3\FLOW3\Package\PackageManagerInterface */ protected $packageManager;
/** * Creates a new package * * Creates a new package with the given package key. The package key * should be the vendor namespace segments. * * @param string $packageKey The package key of the package to create * @return string */ public function createCommand($packageKey) { if (!$this->packageManager->isPackageKeyValid($packageKey)) { $this->response->appendContent('The package key "' . $packageKey . '" is not valid.');
San Francisco, USA
Security
Touchless Security, Flow-Style
• security is handled at a central place (through AOP)
• third-party code is as secure as possible by default
• modeled after our experiences in the TYPO3 project and Spring Security (Java framework)
• provides authentication, authorization, validation, filtering ...
• can intercept arbitrary method calls
• transparently filters content through query-rewriting
• extensible for new authentication or authorization mechanisms
San Francisco, USA
Security Policy
San Francisco, USA
Security
Cross-Site Request Forgery
• enables an attacker to execute privileged operations without being authenticated
• the risk lies in using malicious links or forms while still being authenticated
• imagine a link coming in through an URL shortener...
San Francisco, USA
Security
Avoiding Cross-Site Request Forgery
• add a (truly!) random string token to each link or form
• make sure this token is correct before executing anything
• change the token as often as possible to make it impossible to send you a working malicious link while you’re logged in
• in most cases, we can assume that it should be enough to generate one token when you log in – that’s the default
San Francisco, USA
Security
CSRF Protection in FLOW3
• you must not forget to add that token to any link
• FLOW3 automatically adds the CSRF token to each
• link you generate
• each form you create with Fluid
• and checks it for every call to a protected action
• the protection can be disabled using @skipCsrfProtection on an action
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 18
Users and Login
San Francisco, USA
AOP
Aspect-Oriented Programming
• programming paradigm
• separates concerns to improve modularization
• OOP modularizes concerns into objects
• AOP modularizes cross-cutting concerns into aspects
• FLOW3 makes it easy (and possible at all) to use AOP in PHP
San Francisco, USA
AOP
FLOW3 uses AOP for ...
• persistence magic
• logging
• debugging
• security
/** * @aspect * @introduce F3\FLOW3\Persistence\Aspect\PersistenceMagicInterface, F3\FLOW3\Persistence\Aspect\
*/class PersistenceMagicAspect { /** * @pointcut classTaggedWith(entity) || classTaggedWith(valueobject) */ public function isEntityOrValueObject() {} /** * @var string * @Id * @Column(length="40") * @introduce F3\FLOW3\Persistence\Aspect\PersistenceMagicAspect->isEntityOrValueObject && filter
*/ protected $FLOW3_Persistence_Identifier; /** * After returning advice, making sure we have an UUID for each and every entity.
* * @param \F3\FLOW3\AOP\JoinPointInterface $joinPoint The current join point
* @return void * @before classTaggedWith(entity) && method(.*->__construct()) */ public function generateUUID(\F3\FLOW3\AOP\JoinPointInterface $joinPoint) {
$proxy = $joinPoint->getProxy(); \F3\FLOW3\Reflection\ObjectAccess::setProperty($proxy, 'FLOW3_Persistence_Identifier',
}
K. Dambekalns & R. LemkeD.P. Fluxtr
time();
5 1 18
The Wizard of AOP
San Francisco, USA
Signal-Slot Event Handling
Signal
• can be fired on any event
• can be freely defined by the developer
Slot
• is invoked when a signal is emitted
• any method can be used as a slot
any signal can be wired to any slot
San Francisco, USA
Signal-Slot Event Handling
/** * @param \F3\Blog\Domain\Model\Post $post * @param \F3\Blog\Domain\Model\Comment $newComment * @return void */ public function createAction(\F3\Blog\Domain\Model\Post $post, \F3\Blog\Domain\Model\Comment $newComment) { $post->addComment($newComment); $this->emitCommentCreated($newComment, $post); … }
/** * @param \F3\Blog\Domain\Model\Comment $comment * @param \F3\Blog\Domain\Model\Post $post * @return void * @signal */ protected function emitCommentCreated(\F3\Blog\Domain\Model\Comment $comment, \F3\Blog\Domain\Model\Post $post) {}
San Francisco, USA
Signal-Slot Event Handling
/** * Invokes custom PHP code directly after the package manager has been * initialized. * * @param \F3\FLOW3\Core\Bootstrap $bootstrap The current bootstrap * @return void */ public function boot(\F3\FLOW3\Core\Bootstrap $bootstrap) { $dispatcher = $bootstrap->getSignalSlotDispatcher(); $dispatcher->connect( 'F3\Blog\Controller\CommentController', 'commentCreated', 'F3\Blog\Service\Notification', 'sendNewCommentNotification' ); }
Signals are wired to Slots in a package’s bootstrap:
San Francisco, USA
Signal-Slot Event Handling
/** * @param \F3\Blog\Domain\Model\Comment $comment * @param \F3\Blog\Domain\Model\Post $post * @return void */ public function sendNewCommentNotification(\F3\Blog\Domain\Model\Comment $comment, \F3\Blog\Domain\Model\Post $post) { $mail = new \F3\SwiftMailer\Message(); $mail ->setFrom(array('[email protected] ' => 'John Doe')) ->setTo(array('[email protected] ' => 'Karsten Dambekalns')) ->setSubject('New comment on blog post "' . $post->getTitle() . '"') ->setBody($comment->getContent()) ->send(); }
Any method can be a slot:
San Francisco, USA
Speed and Performance
For the snappy user experience:
• multi-layered, tagged caches
• various cache backends (file, Memcached, APC, Redis, PDO, ...)
• reverse-proxy support (Varnish, ESI) in the works
• code compilation
• regular benchmarks
• focus on good scalability
San Francisco, USA
More Features
• Resource Management (CDNs, private resources, ...)
• Logging
• File Monitoring
• Configuration Management
• Routing
• REST / SOAP
• ...
San Francisco, USA
Roadmap
http://forge.typo3.org/projects/flow3-distribution-base/roadmap
San Francisco, USA
Discover the source code of the conference app
git://git.typo3.org/TYPO3v5/Distributions/Conference.git
San Francisco, USA
Try out the blog app
git://git.typo3.org/FLOW3/Applications/Blog.git
San Francisco, USA
Thank You!
• These slides: http://slideshare.net/robertlemke
• Download FLOW3: http://flow3.typo3.org
• Follow us on Twitter: @t3rob (Robert) @k_fish (Karsten)
• Give us feedback:
• [email protected] / [email protected]
• http://joind.in/3535