Translations
Info
All page names need to be in English.
en da  de  fr  it  ja  km  nl  ru  zh

Flow Cookbook

From TYPO3Wiki
Jump to: navigation, search

This is a page to collect potential Flow Cookbook recipes.

Configuration

Inject the settings of a different package

For example, you want to access the settings of the TYPO3.Flow Package inside your own package:

/**
 * @Flow\Inject
 * @var \TYPO3\Flow\Configuration\ConfigurationManager
 */
protected $configurationManager; 
/**
 * @var array
 */
protected $settings = array();
/**
 * Inject the settings
 *
 * @return void 
 */
public function initializeObject() {
   $this->settings = $this->configurationManager->getConfiguration(\TYPO3\Flow\Configuration\ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'TYPO3.Flow');
} 

Model and Repository

Create unidirectional one-to-many relations

As Domain Driven Design principles prescribe, bidirectional relations should be avoided wherever possible. In order to implement unidirectional one-to-many relations, you have to annotate Doctrine to use a many-to-many relation with a specific additional annotation, see also http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#one-to-many-unidirectional-with-join-table.

Since TYPO3 Flow adds additional simplifications to the required annotations, you only need to note the following:

<?php
/**
 * @Flow\Entity
 */
class Car {

	/**
	 * @ORM\ManyToMany
	 * @ORM\JoinTable(inverseJoinColumns={@ORM\JoinColumn(unique=true)})
	 * @var \Doctrine\Common\Collections\Collection<\Acme\VehicleManager\Domain\Model\Tire>
	 */
	protected $tires;
}

Create multiple objects with one Fluid form

"I have two models Customer and CustomerUser and I want to create a Register.html Fluid Template to create both [...]"

The easiest solution is probably to create a so called "Data Transfer Object":http://en.wikipedia.org/wiki/Data_Transfer_Object. I usually store them in a folder "Dto" (untested example):

<?php
namespace Your\Package\Domain\Dto;

class CustomerAndUserDto {

   /**
    * @var \Your\Package\Domain\Model\Customer
    */
   protected $customer;

   /**
    * @var \Your\Package\Domain\Model\CustomerUser
    */
   protected $customerUser;
   
   /**
    * @param \Your\Package\Domain\Model\Customer $customer
    * @param \Your\Package\Domain\Model\CustomerUser $customerUser
    */
   public function __construct(\Your\Package\Domain\Model\Customer $customer, \Your\Package\Domain\Model\CustomerUser $customerUser) {
     $this->customer = $customer;
     $this->customerUser = $customerUser;
   }
   
   // getters & setters for customer and customerUser

}
?>

Note: The Dto is not persisted, it must not have an @Flow\Entity annotation.

and then in the Fluid form:

<f:form action="create" objectName="newCustomerAndUser">
 <f:form.textfield property="customer.someCustomerProperty" />
 <f:form.textfield property="customerUser.someUserProperty" />
 ...
</f:form>

Note: You won't be able to use the Dto as link arguments (because they are not stored in the Database). Instead you would link to one or more related entities and construct the Dto in the controller:

Edit link in Fluid:

<f:link.action action="edit" arguments="{customer: customer, customerUser: customerUser}">Edit customer & user</f:link.action>

And in the controller:

<?php
namespace Your\Package\Controller;

class CustomerController extends ActionController {
   
   /**
    * @param \Your\Package\Domain\Model\Customer $customer
    * @param \Your\Package\Domain\Model\CustomerUser $customerUser
    * @return void
    */
   public function editAction(\Your\Package\Domain\Model\Customer $customer, \Your\Package\Domain\Model\CustomerUser $customerUser) {
     $customerAndUser = new \Your\Package\Domain\Dto\CustomerAndUserDto($customer, $customerUser);
     $this->view->assign('customerAndUser', $customerAndUser);
   }

and then in the Fluid edit form:

<f:form action="update" object="{customerAndUser}" objectName="customerAndUser">
 <f:form.textfield property="customer.someCustomerProperty" />
 <f:form.textfield property="customerUser.someUserProperty" />
 ...
</f:form>

And then in Controller:

   /**
    * @param \Your\Package\Domain\Dto\CustomerAndUserDto $customerAndUser
    * @return void
    */
   public function updateAction(\Your\Package\Domain\Dto\CustomerAndUserDto $customerAndUser) {
     $this->customerRepository->update($customerAndUser->getCustomer());
     $this->customerUserRepository->update($customerAndUser->getCustomerUser());
     // persist and redirect
   }

The advantage is that you have mapping & validation errors in one place and that you can create a base validator for the Dto.

Use custom table and field names rather than auto generated ones

"I want to use custom table and field names in the ORM mapping to suit an existing database?"

Simply use the Table and Column annotations from Doctrine, they work as usual also in Flow.

Use auto increment fields together with the Flow identifier (UUID) field

"I want to use custom auto increment fields but keep using the Flow persistence identifiers - how?"

You need to add a DQL snippet using the Column annotation:

   @ORM\Column(unique=true, columnDefinition="INT(11) NOT NULL AUTO_INCREMENT UNIQUE")

Since Flow 3.0.0-beta2 (maybe earlier) the following annotation also seeme to be working fine:

   /**
    * Additional ID field with auto-generated value
    * @ORM\Id
    * @var integer
    * @ORM\GeneratedValue(strategy="AUTO")
    * @ORM\Column(type="integer")
    */
   protected $id;

Controller

Avoid redirection to referring form on ActionController validation errors

"Is there a way to avoid redirection to original form when createAction throws an error? I want to stay in the create action."

You could either skip validation by adding a @Flow\IgnoreValidation("$argumentName") annotation to your createAction (see documentation). Or you could override the errorAction of the default ActionController.

Determine the current application context

"How can I detect the current application context?"

The official way as of FLOW3 1.1 is using the getContext() method of the Bootstrap. Just inject the `\TYPO3\FLOW3\Core\Bootstrap` into your controller and call that method. As of TYPO3 Flow 2.0, there is a `\TYPO3\Flow\Utility\Environment` Utility class which contains a `getContext` method you can use to get the applications context.

Creating JSON response (e.g. for AJAX requests)

"I want to trigger particular actions via AJAX while expecting JSON response "

For this, you basically have two options, one is just calling a usual ActionController, but responding with JSON instead of HTML; the other is to use a so-called AJAX Widget.

Using usual action controllers

Just prepare a controller as usual, with the following additional properties and behaviors:

<?php
class ASimpleController extends \TYPO3\Flow\Mvc\Controller\ActionController {
	/**
	 * !!! This property behavior has changed in [see], was formerly named $supportedFormats
	 * @var array
	 * @see https://git.typo3.org/Packages/TYPO3.Flow.git?a=commit;h=03b6d85916e46ed8b2e99bc549d7248957dca935
	 */
	protected $supportedMediaTypes = array('text/html', 'application/json');

	/**
	 * @var array
	 */
	protected $viewFormatToObjectNameMap = array('json' => 'TYPO3\Flow\Mvc\View\JsonView');

	/**
	 * the JSON output then will be {"foo":"bar"}.
	 */
	public function fooSampleAction(...) {
			$this->view->assign('value', array('foo' => 'bar'));
	}
}
?>

Some explanations on this;

  • $supportedMediaTypes makes the controller responsible for the mentioned media types, this means that your request must be adjusted to send the application/json header additionally. Common JavaScript libraries do this on their own if you send an AJAX request via them. If you expect this controller to be used, but send a header not contained in $supportedMediaTypes, you'll get a 406 Not Acceptable.
  • $viewFormatToObjectNameMap just defines what processor is used for what kind of type. While .html is defaultly mapped to the FluidTemplateView, you have to define .json mapping to the JsonView on your own.
  • fooSampleAction is of course your actual intended action, where you assign your output, intended to result in JSON, to the 'value' template variable. If you need to change that variable name for architectural reasons you can also do that; have a look into the TYPO3\Flow\MVC\View\JsonView for more information on this (hint: see the setVariablesToRender() method).
handling validation errors that occur

For handling validation errors and returning these as JSON, you can implement the default `errorAction` and build your error JSON:

<?php
		// ...
	protected function errorAction() {
			// ...
		foreach ($this->arguments->getValidationResults()->getFlattenedErrors() as $propertyPath => $errors) {
			foreach ($errors as $error) {
				/* @var $error \TYPO3\Flow\Validation\Error */
					// collect your errors here the way you want,
					// maybe with error code and message; or translated...
			}
		}
		$this->view->assign('value', array('errors' => $yourCollectedErrorMessages));
	}
?>

With an AJAX Widget

so far, see the official documentation on how to use (Ajax) Widgets: http://flow.typo3.org/documentation/guide/partiii/templating.html#ajax-widgets

View

Change the template of a ViewHelper

With https://review.typo3.org/#/c/16392/ a view configuration was introduced. You can override the template using the following configuration in Configuration/Views.yaml of your package:

# Change the template of the PaginateViewHelper
-
  requestFilter: 'isPackage("TYPO3.Fluid") && isSubPackage("ViewHelpers\Widget") && isController("Paginate") && isAction("index")'
  options:
    templatePathAndFilename: 'resource://My.Package/Private/Templates/ViewHelpers/Widget/Paginate/Index.html'

Using partials across packages

for using partials across packages add in your packages, which supply other Packages with partials, a Views.yaml with following stuff:

-
  options:
    partialRootPaths:
      'You.Package/Partials': 'resource://You.Package/Private/Partials'

If your partial has unique name Fluid will find this partial. If you have multiple partials in different packages with same name, then put the partials in subfolders to facilitate finding a requered partial:

...
<f:render partial="SubfolderFromPartialRootPath/NavBarLoginBox" />
...

Note: Fluid uses first partial, which it finds.

Persistence

Using multiple persistence backends / database connections

"I want to use multiple persistence backends / connections at the same time. How can I do this?"

See Multiple persistence backends for more information on that.

Execute arbitrary DQL

In your repository of choice, inject Doctrine's EntityManager via

/**
 * @Flow\Inject
 * @var \Doctrine\Common\Persistence\ObjectManager
 */
protected $entityManager;

(be aware that ``\Doctrine\Common\Persistence\ObjectManager`` is an interface and resolves to ``\Doctrine\ORM\EntityManager`` per default, that's why)

Then, in your actual ``findBy..`` method, you can, for example,

$dql = 'SELECT party FROM TYPO3\Party\Domain\Model\Party party WHERE ....';
/** @var $query \Doctrine\ORM\Query */
$query = $this->entityManager->createQuery($dql);
$query->setParameters(array(
	'some' => $exampleParameter
));
return $query->execute();


Execute native SQL statements of type DELETE, UPDATE, INSERT

(For other native SQL statements, you can use the native SQL API: http://docs.doctrine-project.org/en/latest/reference/native-sql.html)

In your repository of choice, inject Doctrine's EntityManager via

/**
 * @Flow\Inject
 * @var \Doctrine\Common\Persistence\ObjectManager
 */
protected $entityManager;

(be aware that ``\Doctrine\Common\Persistence\ObjectManager`` is an interface and resolves to ``\Doctrine\ORM\EntityManager`` per default, that's why)

Then, in your actual method, you can, for example,

$sql = 'INSERT INTO vendor_packagename_domain_model_table ...';
/** @var $query \Doctrine\DBAL\Connection */
$sqlConnection = $this->entityManager->getConnection();
$sqlConnection->executeUpdate($sql);

Of course this should be avoided whenever possible but can be useful for bulk operations with many records where best performance is a criterion.

Debug Querying

Enable SQL logging

"For debugging purposes, I want to check what actual SQL requests are sent agains the persistence backend."

Modify your Settings.yaml according to this:

TYPO3:
  Flow:
    persistence:
      doctrine:
        sqlLogger: TYPO3\Flow\Persistence\Doctrine\Logging\SqlLogger

After this, you will find the logs in your Data/ directory.

Get actual DQL or SQL Queries

To get a clue what happens, it might be helpful to get the DQL or even SQL representation of a QOM object. This is how you could debug:

/** @var $query \TYPO3\Flow\Persistence\Doctrine\Query */

/** @var $doctrineQueryBuilder \Doctrine\ORM\QueryBuilder */
$doctrineQueryBuilder = ObjectAccess::getProperty($query, 'queryBuilder', TRUE);

/** @var $doctrineQuery \Doctrine\ORM\Query */
$doctrineQuery = $doctrineQueryBuilder->getQuery();

\TYPO3\Flow\var_dump($doctrineQuery->getDQL());
\TYPO3\Flow\var_dump($doctrineQuery->getSQL());

Resource Management

Upload an image as TYPO3.Media's Image property

Consider the following example model:

/**
 * A Product
 *
 * @Flow\Entity
 */
class Product {

	/**
	 * @var string
	 */
	protected $title;

	/**
	 * @ORM\OneToOne(cascade={"persist"})
	 * @var \TYPO3\Media\Domain\Model\Image
	 */
	protected $image;

If you want to make the image uploadable, use the following Fluid code:

<f:form name="newProduct" action="create" enctype="multipart/form-data">
	<f:form.upload property="image.resource" />
	<f:form.submit />
</f:form>

(while the appropriate action is ``public function createAction(\Bugdemo\UpdateProxyObject\Domain\Model\Product $newProduct)``

The reason for this is, that the TypeConverter which is responsible for handling f:form.upload uploads, the ResourceTypeConverter, is not meant to convert the image at all. The Image's `resource` property, however, is of the intended Resource type, so we set the f:form.upload property to make it the Image's resource property.


Session Handling

Keep User sessions active (even after Surf deployments)

If you're using Surf to deploy your TYPO3 Flow project all active sessions will be destroyed it you deploy a new release. This is caused by the fact that Flow saves all sessions to internal temp folders (Data/Temporary/<Context>/Cache/Data/Flow_Session_*) that gets switched to a new folder during the Surf deployment. To ensure that Data/Session (or whatever you call your Session folder) is a symlink that points to the shared-folder it is necessary to include the steps from this gist to your deployment: https://gist.github.com/DavidSporer/2e6298bbfb08522ca3e8 It first creates the folder you've defined in your Caches.yaml and then symlinks it to the folder in the shared-folder.

Flow 2.2 +

As referenced here: https://gist.github.com/aertmann/18d9c51281402e07338a you may change the Caches.yaml to store the Sessions in a folder which is not handled by surf (shared folder).

 Flow_Session_Storage:
   backendOptions:
     cacheDirectory: '%FLOW_PATH_DATA%Session/Flow_Session_Storage'
 Flow_Session_MetaData:
   backendOptions:
     cacheDirectory: '%FLOW_PATH_DATA%Session/Flow_Session_MetaData'


Please note: If you are using domain objects with session scope, the session cache must be cleared when changes are made to these classes. If you deploy this configuration that will not happen automatically.

Flow < 2.0

The solution is to set a custom session save path in your projects Settings.yaml like this:

 TYPO3:
   Flow:
     session:
       PhpSession:
         savePath: '/tmp'

With your global session save path your sessions will stay active - even if you deploy a new release.

Flow >= 2.0

The solution above doesn't work in TYPO3 Flow 2.0 anymore (see: https://forge.typo3.org/issues/40418#note-6), because the PhpSession has been replaced by a PHP-only implementation.

Due to performance problems, most cache backends are not usable for sessions anymore (as of June 2013; have a look at commit d84108f8e6a52c946f8465793a41ca4e4fe26081 for more details), because they cannot iterate over cache entries (i.e. they don't implement IterableBackendInterface. Currently (March 2014) only the file and APC backends implement this interface. There is a patch pending to make the Redis backend compatible again (https://review.typo3.org/27431).

But you can also assign your own Cache-Backend specifically for Sessions by applying the following config in your Caches.yaml:

 # Flow_Session_*
 Flow_Session_MetaData:
   backend: Foo\Bar\Cache\Backend\SessionFileBackend
 Flow_Session_Storage:
   backend: Foo\Bar\Cache\Backend\SessionFileBackend

and a SessionFileBackend extending \TYPO3\Flow\Cache\Backend\FileBackend. The custom path can be overriden in the setCache-function.

 /**
  * Do not clear the session cache, if this function is called by $this->cacheManager->flushCaches();
  */
 public function flush() {
   $callers = debug_backtrace();		
   if($callers[2]['function'] !== 'flushCaches') {
     \TYPO3\Flow\Utility\Files::emptyDirectoryRecursively($this->cacheDirectory);
   }
 }

Security

Authenticate an account immediately after creation

"I'm trying to authenticate an account after it has been created, so the user doesn't need to log in again. I'm using normal username/password authentication."

$authenticationTokens = $this->securityContext->getAuthenticationTokensOfType('TYPO3\Flow\Security\Authentication\Token\UsernamePassword');
if (count($authenticationTokens) === 1) {
    $authenticationTokens[0]->setAccount($account);
    $authenticationTokens[0]->setAuthenticationStatus(\TYPO3\Flow\Security\Authentication\TokenInterface::AUTHENTICATION_SUCCESSFUL);
}

$this->redirect('edit', 'Account');

Also make sure that a session is started (let the session interface be injected into your object, call start() on it). Example:

/**
 * @Flow\Inject
 * @var \TYPO3\Flow\Session\SessionInterface
 */
protected $session;
$this->session->start();

Create own authentication handling (i.e. login/logout)

"How can I create Login/Logout it with my own Cryptograph, Controller, provider and token?"

This example is up to date: https://git.typo3.org/Packages/TYPO3.Blog.git/blob/HEAD:/Classes/TYPO3/Blog/Controller/LoginController.php

Add login functionality for Flow application

If you don't want to do everything yourself, you might check out the Flow.Login package based on Bootstrap: https://github.com/svparijs/Flow.Login It includes a normal login page as well as a modal login box.