Date post: | 08-May-2015 |
Category: |
Technology |
Upload: | robert-lemke |
View: | 4,912 times |
Download: | 1 times |
Fluent Development WithFLOW3 1.0
Robert Lemke
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
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
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
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.
Hello World!
Package.php
<?phpnamespace F3\Demo;
use \F3\FLOW3\Package\Package as BasePackage;
class Package extends BasePackage {}
$ ./flow3_dev flow3:package:create Demo
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
Hello World!
http://dev.flow3.rob/demo/standard/index?name=Robert
Hello Robert!
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 submmited 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; }}?>
Domain-Driven Design
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
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);
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
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;}
?>
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;}
?>
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;}
?>
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;}
?>
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
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)
<?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 ...)
<?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 ...)
<?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
Constructor Injection
<?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
<?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
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
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)
class Customer {
/** * @inject * @var CustomerNumberGenerator */ protected $customerNumberGenerator;
...}
$customer = new Customer();
Object Management
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
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
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',
}
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
Security Policy
The Zen of Templating
FLOW3 comes with an elegant, flexible and secure templating engine: Fluid
• templates are valid HTML
• templates contain no PHP
• object access, control structures, loops ...
• designer-friendly
• extensible (view helpers, widgets)
The Zen of Templating
...<body> <h1>Latest Blog Posts</h1> <f:if condition="{posts}"> <f:then> <ol> <f:for each="{posts}" as="post"> <li class="post"> <h2> <f:link.action action="show" controller="Post" arguments="{post: post}">{post.title}</f:link.action> <f:security.ifHasRole role="Editor"> <f:link.action action="edit" arguments="{post: post}" controller="Post"><img src="{ <f:link.action onclick="return confirm('Really delete this post?');" action="delete" arguments="{post: post}" </f:security.ifHasRole> </h2> <f:render partial="PostMetaData" arguments="{post: post}"/> <f:link.action action='show' arguments='{post: post}'>Read more</f:link.action></p> </li> </f:for> </ol> </f:then> <f:else> <p>This blog currently doesn't contain any posts.
<f:link.action action="new" controller="Post">Create the first post</f:link.action></p>
</f:else> </f:if></f:section>
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;
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>
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'); }
}?>
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 scalibility
More Features
Command Line Support(including interactive shell!)
More Features
• Resource Management (CDNs, private resources, ...)
• Logging
• Caching
• Signal Slot event handling
• File Monitoring
• Configuration Management
• Routing
• REST / SOAP
• ...
Roadmap
http://forge.typo3.org/projects/flow3-distribution-base/roadmap
Live Projects?
Thank You!
• These slides: http://slideshare.net/robertlemke
• Download FLOW3: http://flow3.typo3.org
• Follow me on Twitter: @t3rob
• Give me feedback:
• http://joind.in/3492