The Real World Beyond the Blog Example Robert Lemke_
May 19, 2015
The Real WorldBeyond the Blog Example
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
"the leftovers talks"
TEXT HERE
Application Structure
Request / Responseand MVC
$context = getenv('FLOW_CONTEXT') ?: (getenv('REDIRECT_FLOW_CONTEXT') ?: 'Development');$bootstrap = new \TYPO3\Flow\Core\Bootstrap($context);$bootstrap->run();
/** * Bootstraps the minimal infrastructure, resolves a fitting request handler and * then passes control over to that request handler. * * @return void * @api */public function run() { Scripts::initializeClassLoader($this); Scripts::initializeSignalSlot($this); Scripts::initializePackageManagement($this);
$this->activeRequestHandler = $this->resolveRequestHandler(); $this->activeRequestHandler->handleRequest();}
/** * Handles a HTTP request * * @return void */public function handleRequest() { $this->request = Request::createFromEnvironment(); $this->response = new Response();
$this->boot(); $this->resolveDependencies(); $this->request->injectSettings($this->settings);
$this->router->setRoutesConfiguration($this->routesConfiguration); $actionRequest = $this->router->route($this->request); $this->securityContext->setRequest($actionRequest);
$this->dispatcher->dispatch($actionRequest, $this->response);
$this->response->makeStandardsCompliant($this->request); $this->response->send();
$this->bootstrap->shutdown('Runtime'); $this->exit->__invoke();}
/** * Dispatches a request to a controller * * @param \TYPO3\Flow\Mvc\RequestInterface $request The request to dispatch * @param \TYPO3\Flow\Mvc\ResponseInterface $response The response, to be modified by the controller * @return void * @throws \TYPO3\Flow\Mvc\Exception\InfiniteLoopException * @api */public function dispatch(RequestInterface $request, ResponseInterface $response) { $dispatchLoopCount = 0; while (!$request->isDispatched()) { $controller = $this->resolveController($request); try { $this->emitBeforeControllerInvocation($request, $response, $controller); $controller->processRequest($request, $response); $this->emitAfterControllerInvocation($request, $response, $controller); } catch (StopActionException $exception) { $this->emitAfterControllerInvocation($request, $response, $controller); if ($exception instanceof ForwardException) { $request = $exception->getNextRequest(); } elseif ($request->isMainRequest() === FALSE) { $request = $request->getParentRequest(); } } }}
/** * Book controller for the RobertLemke.Example.Bookshop package */class BookController extends ActionController {
/** * @Flow\Inject * @var \RobertLemke\Example\Bookshop\Domain\Repository\BookRepository */ protected $bookRepository;
/** * Shows a single book object * * @param \RobertLemke\Example\Bookshop\Domain\Model\Book $book The book to show * @return void */ public function showAction(Book $book) { $this->view->assign('book', $book); }
}
/** * Dispatches a request to a controller * * @param \TYPO3\Flow\Mvc\RequestInterface $request The request to dispatch * @param \TYPO3\Flow\Mvc\ResponseInterface $response The response, to be modified by the controller * @return void * @throws \TYPO3\Flow\Mvc\Exception\InfiniteLoopException * @api */public function dispatch(RequestInterface $request, ResponseInterface $response) { $dispatchLoopCount = 0; while (!$request->isDispatched()) { $controller = $this->resolveController($request); try { $this->emitBeforeControllerInvocation($request, $response, $controller); $controller->processRequest($request, $response); $this->emitAfterControllerInvocation($request, $response, $controller); } catch (StopActionException $exception) { $this->emitAfterControllerInvocation($request, $response, $controller); if ($exception instanceof ForwardException) { $request = $exception->getNextRequest(); } elseif ($request->isMainRequest() === FALSE) { $request = $request->getParentRequest(); } } }}
/** * Handles a HTTP request * * @return void */public function handleRequest() { $this->request = Request::createFromEnvironment(); $this->response = new Response();
$this->boot(); $this->resolveDependencies(); $this->request->injectSettings($this->settings);
$this->router->setRoutesConfiguration($this->routesConfiguration); $actionRequest = $this->router->route($this->request); $this->securityContext->setRequest($actionRequest);
$this->dispatcher->dispatch($actionRequest, $this->response);
$this->response->makeStandardsCompliant($this->request); $this->response->send();
$this->bootstrap->shutdown('Runtime'); $this->exit->__invoke();}
namespace Acme\Demo;
class InvoiceController extends \Acme\Demo\Controller\AbstractBaseController {
…
/** * GeneratePdf action * * @param \Acme\Demo\Domain\Model\Invoice $invoice * @param boolean $forward */ public function generatePdfAction(\Acme\Demo\Domain\Model\Invoice $invoice, $forward = TRUE) { $fopCommand = $this->settings['pdf']['fopCommand'];
$storageInvoiceFilename = $this->renderInvoiceFilename($invoice);
$outputPath = $this->environment->getPathToTemporaryDirectory() . 'Acme.Demo/';
$packageResourcesPath = $this->packageManager->getPackage('Acme.Demo')->getResourcesPath(); $xmlPathAndFilename = $outputPath . 'Xml/'. $storageInvoiceFilename . '.xml'; $configurationPathAndFilename = $outputPath . 'Configuration.xml'; $pdfPathAndFilename = $outputPath.'Pdf/'. $storageInvoiceFilename; $fontsPath = $packageResourcesPath . 'Private/Fop/Fonts/'; $xslPathAndFilename = $packageResourcesPath . 'Private/Fop/Xsl/Document.xsl';
if (!file_exists($outputPath . 'Pdf')){ Files::createDirectoryRecursively($outputPath . 'Pdf'); } if (!file_exists($outputPath . 'Xml')){ Files::createDirectoryRecursively($outputPath . 'Xml'); }
$standaloneView = new StandaloneView(); $standaloneView->setTemplatePathAndFilename('resource://Acme.Demo/Private/Fop/Xml/Document.xml'); $standaloneView->assign('invoice', $invoice);
file_put_contents($xmlPathAndFilename, $standaloneView->render());
$standaloneView = new StandaloneView(); $standaloneView->setTemplatePathAndFilename('resource://Acme.Demo/Private/Fop/Configuration.xml'); $standaloneView->assign('fontPath', $fontsPath);
file_put_contents($configurationPathAndFilename, $standaloneView->render());
$command = $fopCommand . ' -q -c ' . $configurationPathAndFilename . ' -xml ' . $xmlPathAndFilename . ' -xsl ' . $xslPathAndFilename . ' -pdf ' . $pdfPathAndFilename; exec($command);
$pdf = $this->resourceManager->importResource($pdfPathAndFilename, 'DemoInvoices'); $invoice->setPdf($pdf);
if($forward) { $this->forward('download', NULL, NULL, array('invoice' => $invoice)); } }
namespace Acme\Demo;
class InvoiceController extends \Acme\Demo\Controller\AbstractBaseController {
/** * @var \Acme\Demo\Application\Service\InvoiceGenerator * @Flow\Inject */ protected $invoiceGenerator;
/** * GeneratePdf action * * @param \Acme\Demo\Domain\Model\Invoice $invoice * @param boolean $forward */ public function generatePdfAction(\Acme\Demo\Domain\Model\Invoice $invoice, $forward = TRUE) { $this->invoiceGeneratorService->generate($invoice, $pdf); $this->forward('download', NULL, NULL, array('invoice' => $invoice)); }
Forms
TEXT HERE
HTTP Caches
$this->response->getHeaders()->setCacheControlDirective('s-max-age', 100);
Safe Request / method tunneling
Network Working Group R. FieldingRequest for Comments: 2616 UC IrvineObsoletes: 2068 J. GettysCategory: Standards Track Compaq/W3C J. Mogul Compaq H. Frystyk W3C/MIT L. Masinter Xerox P. Leach Microsoft T. Berners-Lee W3C/MIT June 1999
Hypertext Transfer Protocol -- HTTP/1.1
Status of this Memo
This document specifies an Internet standards track protocol for the Internet community, and requests discussion and suggestions for improvements. Please refer to the current edition of the "Internet Official Protocol Standards" (STD 1) for the standardization state and status of this protocol. Distribution of this memo is unlimited.
Copyright Notice
Copyright (C) The Internet Society (1999). All Rights Reserved.
Abstract
The Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol which can be used for many tasks beyond its use for hypertext, such as name servers and distributed object management systems, through extension of its request methods, error codes and headers [47]. A feature of HTTP is the typing and negotiation of data representation, allowing systems to be built independently of the data being transferred.
HTTP has been in use by the World-Wide Web global information initiative since 1990. This specification defines the protocol referred to as "HTTP/1.1", and is an update to RFC 2068 [33].
9.1 Safe and Idempotent Methods
9.1.1 Safe Methods
Implementors should be aware that the software represents the user in their interactions over the Internet, and should be careful to allow the user to be aware of any actions they might take which may have an unexpected significance to themselves or others.
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.
Naturally, it is not possible to ensure that the server does not generate side-effects as a result of performing a GET request; in fact, some dynamic resources consider that a feature. The important distinction here is that the user did not request the side-effects, so therefore cannot be held accountable for them.
9.1.2 Idempotent Methods
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property. Also, the methods OPTIONS and TRACE SHOULD NOT have side effects, and so are inherently idempotent.However, it is possible that a sequence of several requests is non- idempotent, even if all of the methods executed in that sequence are idempotent. (A sequence is idempotent if a single execution of the entire sequence always yields a result that is not changed by a reexecution of all, or part, of that sequence.) For example, a sequence is non-idempotent if its result depends on a value that is later modified in the same sequence.A sequence that never has side effects is idempotent, by definition (provided that no concurrent operations are being executed on the same set of resources).
no automatic persistence
CSRF
<a href="http://myserver.com/book/amazing-apps-with-flow/delete">Delete Book</a>
<a href="http://myserver.com/book/amazing-apps-with-flow/delete?__csrfToken=abcdef1234567890">Delete Book</a>
<form enctype="multipart/form-data" name="newBook" action="book/create" method="post">
<input type="hidden" name="__trustedProperties" value="a:1:{s:7:"newBook";a:6: <input type="hidden" name="__csrfToken" value="10fa21087d49e5bb37d9c91248ea693a"/>
...
speed!
Lazy Dependency Injection
class BookController extends ActionController {
/** * @Flow\Inject * @var BookRepository */ protected $bookRepository;
}
class BookController extends ActionController {
… public function myAction() { // $this->bookRepository is instance of Dependency Proxy $this->bookRepository->findAll();
// $this->bookRepository is the real BookRepository
}
}
$greet = function($name) { printf("Hello %s", $name);};
$greet('World');
class BookController extends BookController_Original implements ProxyInterface {
/** * Autogenerated Proxy Method */ public function __construct() { $this->Flow_Proxy_injectProperties(); }
$bookRepository_reference = &$this->bookRepository;
$this->bookRepository = Bootstrap::$staticObjectManager->getLazyDependencyByHash('d0e87f8f658d7866eec63db44a6918b4', $bookRepository_reference);
if ($this->bookRepository === NULL) { $this->bookRepository = Bootstrap::$staticObjectManager
->createLazyDependency('d0e87f8f658d7866eec63db44a6918b4', $bookRepository_reference, 'RobertLemke\Example\Bookshop\Domain\Repository\BookRepository', function() {
return Bootstrap::$staticObjectManager->get('RobertLemke\Example\Bookshop\Domain\Repository\BookRepository'
); });
}
class BookController extends ActionController {
… public function myAction() { $this->bookRepository->findAll(); }
}
class DependencyProxy {
… /** * Proxy magic call method which triggers the injection of the real dependency * and returns the result of a call to the original method in the dependency * * @param string $methodName Name of the method to be called * @param array $arguments An array of arguments to be passed to the method * @return mixed */ public function __call($methodName, array $arguments) { return call_user_func_array(array($this->_activateDependency(), $methodName), $arguments); }
/** * Activate the dependency and set it in the object. * * @return object The real dependency object * @api */ public function _activateDependency() { $realDependency = $this->builder->__invoke(); foreach($this->propertyVariables as &$propertyVariable) { $propertyVariable = $realDependency; } return $realDependency; }
}
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);
Roles
roles: User: [] Manager: ['User'] Editor: ['User', 'TYPO3.Neos:Editor']
/** * Create a role and return a role instance for it. * * @param string $roleIdentifier * @return \TYPO3\Flow\Security\Policy\Role * @throws \TYPO3\Flow\Security\Exception\RoleExistsException */public function createRole($roleIdentifier) { $this->initializeRolesFromPolicy();
if (isset($this->systemRoles[$roleIdentifier])) { throw new RoleExistsException(sprintf('Could not create role %s because a system role with }
if (preg_match('/^[\w]+((\.[\w]+)*\:[\w]+)+$/', $roleIdentifier) !== 1) { throw new \InvalidArgumentException(sprintf('Could not create role %s because it does not }
if ($this->roleRepository->findByIdentifier($roleIdentifier) !== NULL) { throw new RoleExistsException(sprintf('Could not create role %s because a role with that }
$role = new Role($roleIdentifier); $this->roleRepository->add($roleIdentifier);
return $role;}
Virtual Browser
/** * @Flow\Inject * @var \TYPO3\Flow\Http\Client\Browser */protected $browser;
/** * @return array */public function getBookInfo($isbn) { $this->browser->setRequestEngine(new CurlEngine()); $response = $this->browser->request(
'http://isbndb.com/api/books.xml?index1=isbn&value1=' . $isbn); $xml = simplexml_load_string($response->getContent());
…
return $bookInfo;}
Page Cache
/** * Shows a list of books * * @return void */public function indexAction() { $this->view->assign('books', $books);}
RobertLemke_Example_Bookshop_Html: frontend: TYPO3\Flow\Cache\Frontend\StringFrontend backend: TYPO3\Flow\Cache\Backend\FileBackend
/** * Shows a list of books * * @return string */public function indexAction() { $output = $this->htmlCache->get('BookController_index'); if ($output === FALSE) { $books = $this->bookRepository->findAll(); $this->view->assign('books', $books); $output = $this->view->render(); $this->htmlCache->set('BookController_index', $output); } return $output;}
RobertLemke\Example\Bookshop\Controller\BookController: properties: htmlCache: object: factoryObjectName: TYPO3\Flow\Cache\CacheManager factoryMethodName: getCache arguments: 1: value: 'RobertLemke_Example_Bookshop_Html'
Ask me anything *
* technical
https://github.com/robertlemke
TYPO3 Flow Trainings and Inhouse Workshops
Robert Lemke Blog
Robert Lemke_robertlemke.com@robertlemke