typo3 flow 2.0 workshop t3board13
DESCRIPTION
Slides of the TYPO3 Flow 2.0 fundamentals workshop which took place at Schmittenhöhe, Austria during T3BOARD13TRANSCRIPT
TYPO3 Flow 2.0
Robert Lemke_
TEXT HERE
project founder of TYPO3 Flow and TYPO3 Neos
co-founder of the TYPO3 Association
coach, coder, consultant
36 years old
lives in Lübeck, Germany
1 wife, 2 daughters, 1 espresso machine
likes drumming
TYPO3 Flow Website and Download
Installation via Composer
$ curl -s https://getcomposer.org/installer | php$ sudo mv composer.phar /usr/local/bin/composer
$ composer create-project --stability="beta" --dev typo3/flow-base-distribution MyProject
Set File Permissions
$ sudo ./flow core:setfilepermissions robert _www _wwwTYPO3 Flow File Permission Script
Checking permissions from here upwards.Making sure Data and Web/_Resources exist.Setting file permissions, trying to set ACLs via chmod ...Done.
$ sudo usermod -a -G www-data robert
$ sudo dscl . -append /Groups/_www GroupMembership robert
Linux:
Mac OS X:
Set Up Database Connection
Configuration/Settings.yaml
TYPO3: Flow: persistence: backendOptions: host: '127.0.0.1' # adjust to your database host dbname: 'training' # adjust to your database name user: 'root' # adjust to your database user password: 'password' # adjust to your database password
# if you want to log executed SQL queries, enable the next 2 lines # doctrine: # sqlLogger: 'TYPO3\Flow\Persistence\Doctrine\Logging\SqlLogger'
# You might need to uncomment the following lines and specify # the location of the PHP binary manually. # core: # phpBinaryPathAndFilename: 'C:/path/to/php.exe'
Set Up Virtual Host
Apache Virtual Host
<VirtualHost *:80> DocumentRoot ~/Sites/Flow/Web/ ServerName flow.dev SetEnv FLOW_CONTEXT Development</VirtualHost>
<VirtualHost *:80> DocumentRoot ~/Sites/Flow/Web/ ServerName flow.prod SetEnv FLOW_CONTEXT Production</VirtualHost>
Final Check
Command Line Use
$ ./flow help kickstart:package
Kickstart a new package
COMMAND: typo3.kickstart:kickstart:package
USAGE: ./flow kickstart:package <package key>
ARGUMENTS: --package-key The package key, for example "MyCompany.MyPackageName"
DESCRIPTION: Creates a new package and creates a standard Action Controller and a sample template for its Index Action. For creating a new package without sample code use the package:create command.
SEE ALSO: typo3.flow:package:create (Create a new package)
Command Line Use
$ ./flow help kickstart:actioncontroller
Kickstart a new action controller
COMMAND: typo3.kickstart:kickstart:actioncontroller
USAGE: ./flow kickstart:actioncontroller [<options>] <package key> <controller name>
ARGUMENTS: --package-key The package key of the package for the new controller with an optional subpackage, (e.g. "MyCompany.MyPackage/Admin"). --controller-name The name for the new controller. This may also be a comma separated list of controller names.
OPTIONS: --generate-actions Also generate index, new, create, edit, update and delete actions. --generate-templates Also generate the templates for each action. --generate-related Also create the mentioned package, related model and repository if neccessary. --force Overwrite any existing controller or template code. Regardless of this flag, the package, model and repository will never be overwritten.
DESCRIPTION: Generates an Action Controller with the given name in the specified package. In its default mode it will create just the controller containing a sample indexAction. By specifying the --generate-actions flag, this command will also create a set of actions. If no model or repository exists which matches the controller name (for example "CoffeeRepository" for "CoffeeController"), an error will be shown. Likewise the command exits with an error if the specified package does not exist. By using the --generate-related flag, a missing package, model or repository can be created alongside, avoiding such an error. By specifying the --generate-templates flag, this command will also create matching Fluid templates for the actions created. This option can only be used in combination with --generate-actions. The default behavior is to not overwrite any existing code. This can be overridden by specifying the --force flag.
SEE ALSO: typo3.kickstart:kickstart:commandcontroller (Kickstart a new command controller)
Biggest Book Store: Amazon
Biggest River: Amazon River
Smallest River: Roe River
Smallest River: Roe River
Smallest River: Roe River
Smallest River: Roe River
Smallest Book Store: Roebooks
Sketchy Model
Robert LemkeD.P. Fluxtr
time();
5 1 12
Hello World …
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
<?php
class SomeService { protected static $instance; public function getInstance() { if (self::$instance === NULL) { self::$instance = new self; } return self::$instance; }}
class SomeOtherController { public function action() { $service = SomeService::getInstance(); … } }
?>
class ServiceLocator { protected static $services = array(); public function getInstance($name) { return self::$service[$name]; }
}
class SomeOtherController { public function action() { $service = ServiceLocator::getInstance("SomeService"); … } }
class BookController extends ActionController {
/** * @var BookRepository */ protected $bookRepository; /** * @param BookRepository $bookRepository */ public function __construct(BookRepository $bookRepository) { $this->bookRepository = $bookRepository; } }
class BookController extends ActionController {
/** * @var BookRepository */ protected $bookRepository; /** * @param BookRepository $bookRepository */ public function injectBookRepository(BookRepository $bookRepository) { $this->bookRepository = $bookRepository; } }
class BookController extends ActionController {
/** * @Flow\Inject * @var BookRepository */ protected $bookRepository;
}
TYPO3\Flow\Security\Cryptography\RsaWalletServiceInterface: className: TYPO3\Flow\Security\Cryptography\RsaWalletServicePhp scope: singleton properties: keystoreCache: object: factoryObjectName: TYPO3\FLOW3\Cache\CacheManager factoryMethodName: getCache arguments: 1: value: FLOW3_Security_Cryptography_RSAWallet
Object Management
Flow'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 {
/** * @Flow\Inject * @var \Acme\CustomerNumberGenerator */ protected $customerNumberGenerator; ...}
$customer = new Customer();$customer->getCustomerNumber();
Object Management
<?phpdeclare(ENCODING = 'utf-8');namespace TYPO3\Conference\Domain\Model\Conference;use TYPO3\Flow\Annotations as Flow;/** * Autogenerated Proxy Class * @Flow\Scope(“prototype”) * @Flow\Entity */class Paper extends Paper_Original implements \TYPO3\Flow\Object\Proxy\ProxyInterface, \TYPO3\Flow\Persistence\Aspect\PersistenceMagicInterface { /** * @var string * @ORM\Id * @ORM\Column(length="40") * introduced by TYPO3\Flow\Persistence\Aspect\PersistenceMagicAspect */ protected $Flow_Persistence_Identifier = NULL; private $Flow_AOP_Proxy_targetMethodsAndGroupedAdvices = array(); private $Flow_AOP_Proxy_groupedAdviceChains = array(); private $Flow_AOP_Proxy_methodIsInAdviceMode = array();
/** * Autogenerated Proxy Method */ public function __construct() { $this->Flow_AOP_Proxy_buildMethodsAndAdvicesArray(); if (isset($this->Flow_AOP_Proxy_methodIsInAdviceMode['__construct'])) { parent::__construct();
} else {
Flow 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
Object Scope
_ prototype: multiple instances for one request
_ singleton: one unique instance for one request
_ session: one unique instance for one session
_ default scope: prototype.
/** * @Flow\Scope("prototype") */class BookController extends ActionController {
Lifecycle Methods
/** * Called after the object has been constructed and all * dependencies have been injected * * @param integer $initializationCause * @return void */ public function initializeObject($initializationCause) { switch ($initializationCause) { case ObjectManagerInterface::INITIALIZATIONCAUSE_CREATED : … case ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED : … } }
Lifecycle Methods
/** * Called shortly before the framework shuts down */ public function shutdownObject() { }
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 TYPO3\Flow\Persistence\Aspect\PersistenceMagicInterface, TYPO3\Flow\Persistence
*/class PersistenceMagicAspect { /** * @Pointcut classTaggedWith(entity) || classTaggedWith(valueobject) */ public function isEntityOrValueObject() {} /** * After returning advice, making sure we have an UUID for each and every entity.
* * @param \TYPO3\Flow\AOP\JoinPointInterface $joinPoint The current join point
* @return void * @Before classTaggedWith(entity) && method(.*->__construct()) */ public function generateUUID(JoinPointInterface $joinPoint) { $proxy = $joinPoint->getProxy(); ObjectAccess::setProperty($proxy, 'Flow_Persistence_Identifier', … }
Aspect
_ part of the application where cross-cutting concerns are implemented
_ in Flow aspects are classes annotated with@Flow\Aspect
Join Point
A single point in the call graph
_ method execution
_ exception
Join Point
A single point in the call graph
_ method execution
_ exception
Represents an event, not a location
Pointcut
A set of join points where advices could be executed
_ can be composed
_ can be named
Advice
Action to take at a join points defined by the point cut
Kinds of Advice
Advice types supported by Flow:
@Flow\Before@Flow\AfterReturning@Flow\AfterThrowing@Flow\After@Flow\Around
Pointcut Designators
method(Acme\Demo\MyClass->myMethod())class(Acme\Demo\MyClass)within(Acme\Demo\MyInterface)classAnnotatedWith(someTag)methodAnnotatedWith(anotherTag)setting(Acme.Demo.SomeSetting = "yeah, do it")filter(Acme\Demo\MyCustomFilterImplementation)
evaluate(coffe.kind = "Arabica")
namespace TYPO3\Flow\Session\Aspect;use TYPO3\Flow\Annotations as Flow;
/** * An aspect which centralizes the logging of important session actions. * * @Flow\Aspect * @Flow\Scope("singleton") */class LoggingAspect {
/** * @var \TYPO3\Flow\Log\SystemLoggerInterface * @Flow\Inject */ protected $systemLogger;
/** * Logs calls of start() * * @Flow\After("within(TYPO3\Flow\Session\SessionInterface) && method(.*->start())") * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current joinpoint */ public function logStart(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) { $session = $joinPoint->getProxy(); if ($session->isStarted()) { $this->systemLogger->log(sprintf('Started session with id %s', $session->getId()), LOG_DEBUG);
} }
Persistence
Object Persistence in the Flow
_ based on Doctrine 2
_ seamless integration into Flow
_ provides all the great Doctrine 2 features
_ uses UUIDs
_ low level persistence API
_ allows for own, custom persistence backends (instead of Doctrine 2)
_ e.g. CouchDB, Solr
// 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);
Annotations
In order to use less code, the following examples assume that annotations have been imported directly:
use TYPO3\Flow\Annotations\Entity;
/** * @Entity */class Foo {}
Validation and Doctrine Annotations
/** * @Entity */class Blog {
/** * @var string * @Validate Text, StringLength(minimum = 1, maximum = 80) * @Column(length="80") */ protected $title;
/** * @var \Doctrine\Common\Collections\Collection<\TYPO3\Blog\Domain\Model\Post> * @OneToMany(mappedBy="blog") * @OrderBy({"date" = "DESC"}) */ protected $posts;
...
}
Persistence-related Annotations
@Entity Declares a class as "entity"
@Column Controls the database column related to the class property. Very useful for longer text content (type="text" !)
@ManyToOne @OneToMany @ManyToMany@OneToOne
Defines relations to other entities. Unlike with vanilla Doctrine targetEntity does not have to be given but will be reused from the @var annotation.
cascade can be used to cascade operation to related objects.
@var Defines the type of a property, collections can be typed using angle brackets
Collection<\TYPO3\Conference\Domain\Model\Comment>
@transient The property will be ignored, it will neither be persisted nor reconstituted
@identity Marks the property as part of an objects identity
Persistence-related Annotations
Custom Queries using theQuery Object Model
class PostRepository extends Repository {
/** * Finds posts by the specified tag and blog * * @param \TYPO3\Blog\Domain\Model\Tag $tag * @param \TYPO3\Blog\Domain\Model\Blog $blog The blog the post must refer to * @return \TYPO3\Flow\Persistence\QueryResultInterface The posts */ public function findByTagAndBlog(\TYPO3\Blog\Domain\Model\Tag $tag, \TYPO3\Blog\Domain\Model\Blog $blog) { $query = $this->createQuery(); return $query->matching( $query->logicalAnd( $query->equals('blog', $blog), $query->contains('tags', $tag) ) ) ->setOrderings(array( 'date' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_DESCENDING) ) ->execute(); }}
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 Flow
Schema Management
Executing migration scripts
Needed after installation or upgrade:
$ ./flow doctrine:migrate
Schema Management
Manual database updates
Ad-hoc table and column creation, while you’re developing:
$ ./flow doctrine:create
$ ./flow doctrine:update
Schema Management
Generating migration scripts
Creates a basis for a migration script which sometimes needs to be adjusted but in any case needs to be checked:
$ ./flow doctrine:migrationgenerate
Security
_ centrally managed (through AOP)
_ as secure as possible by default
_ modeled after TYPO3 CMS and Spring Security
_ authentication, authorization, validation, filtering ...
_ can intercept arbitrary method calls
_ transparently filters content through query-rewriting
_ extensible for new authentication or authorization mechanisms
Accounts, Users, Authentication
Flow distinguishes between accounts and persons:
_ account: \TYPO3\Flow\Security\Account
_ person: \TYPO3\Party\Domain\Model\Person
A person (or machine) can have any number of accounts.
Creating Accounts
_ always use the AccountFactory
_ create a party (eg. a Person) separately
_ assign the account to the party
_ add account and party to their respective repositories
$account = $this->accountFactory->createAccountWithPassword( $accountIdentifier, $password, array($role));
$this->accountRepository->add($account);
$person = new Person();$person->addAccount($account);
$name = new PersonName('', 'Robert', '', 'Lemke');$person->setName($name);
$this->partyRepository->add($person);
Authentication Configuration
_ Authentication Provider is responsible for authentication in a specific "area"
_ Entry Point kicks in if a restricted resource is accessed and no account is authenticated yet
TYPO3: Flow: security: authentication: providers: DefaultProvider: provider: 'PersistedUsernamePasswordProvider' entryPoint: 'WebRedirect' entryPointOptions: routeValues: '@package': 'RobertLemke.Example.Bookshop' '@controller': 'Login' '@action': 'login' '@format': 'html'
Security Policy (policy.yaml)
_ resources defines what can potentially be protected
_ roles defines who can potentially be granted or denied access
_ aclsdefines who may or may not access which resource
resources: methods: BookManagementMethods: 'method(.*Controller->(new|edit|create|delete|update)Action())' BookManagementDelete: 'method(.*BookController->deleteAction())'
roles: Administrator: []
acls: methods: Administrator: BookManagementMethods: GRANT
Login / Logout
_ simply extend AbstractAuthenticationController
_ create a Fluid template with a login form
/** * @Flow\Scope("singleton") */class LoginController extends AbstractAuthenticationController {
/** * @param \TYPO3\Flow\Mvc\ActionRequest $originalRequest The request that * @return string */ protected function onAuthenticationSuccess(ActionRequest $originalRequest = NULL) { $this->redirect('index', 'Book'); }
/** * @return void */ public function logoutAction() { parent::logoutAction(); $this->redirect('index', 'Book'); }}
<f:base/><f:flashMessages /><f:form action="authenticate"> <f:form.textfield name="__authentication[TYPO3][Flow][Security][Authentication][Token][UsernamePassword][username]" /> <f:form.password name="__authentication[TYPO3][Flow][Security][Authentication][Token][UsernamePassword][password]" /> <f:form.submit value="login" /></f:form>
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...
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
Security
CSRF Protection in Flow
_ you must not forget to add that token to any link
_ Flow 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
Robert Lemke_robertlemke.com@robertlemke