Skip to content

Instantly share code, notes, and snippets.

@robertlemke
Forked from bwaidelich/AbstractModelFormFactory.php
Last active December 11, 2015 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robertlemke/4657165 to your computer and use it in GitHub Desktop.
Save robertlemke/4657165 to your computer and use it in GitHub Desktop.
<?php
namespace TYPO3\Form\Factory;
/* *
* This script belongs to the FLOW3 package "TYPO3.Form". *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License, either version 3 *
* of the License, or (at your option) any later version. *
* *
* The TYPO3 project - inspiring people to share! *
* */
use TYPO3\FLOW3\Annotations as FLOW3;
use TYPO3\Form\Core\Model\FormDefinition;
use TYPO3\FLOW3\Validation\Validator as Validator;
/**
* TODO documentation, @api annotation(?)
*/
abstract class AbstractModelFormFactory extends AbstractFormFactory {
/**
* @FLOW3\Inject
* @var \TYPO3\FLOW3\Reflection\ReflectionService
*/
protected $reflectionService;
/**
* @FLOW3\Inject
* @var \TYPO3\FLOW3\Validation\ValidatorResolver
*/
protected $validatorResolver;
/**
* @FLOW3\Inject
* @var \TYPO3\FLOW3\Persistence\PersistenceManagerInterface
*/
protected $persistenceManager;
/**
* @var FormDefinition
*/
protected $formDefinition;
/**
* @var array
*/
protected $configuration = array();
/**
* @var string
*/
protected $objectPrefix = 'object';
/**
* @var object
*/
protected $targetObject = NULL;
/**
* @param array $configuration factory-specific configuration array
* @param string $presetName The name of the "Form Preset" to use; it is factory-specific to implement this.
* @return \TYPO3\Form\Core\Model\FormDefinition a newly built form definition
* @throws \TYPO3\Form\Exception
*/
public function build(array $configuration, $presetName) {
// merge preset configuration with specified $configuration ("overrideConfiguration" argument of the render ViewHelper)
$this->configuration = \TYPO3\FLOW3\Utility\Arrays::arrayMergeRecursiveOverrule($this->getPresetConfiguration($presetName), $configuration);
$formIdentifier = sprintf('%sForm', $this->objectPrefix);
$this->formDefinition = new FormDefinition($formIdentifier, $this->configuration);
$page1 = $this->formDefinition->createPage('page1');
$targetClassName = $this->getTargetClassName();
// set $this->targetObject if specified
if (isset($this->configuration['targetObject'])) {
if (!$this->configuration['targetObject'] instanceof $targetClassName) {
throw new \TYPO3\Form\Exception(sprintf('The specified targetObject must be of type "%s", given: "%s"', $targetClassName, get_class($this->configuration['targetObject'])), 1336394809);
}
$this->targetObject = $this->configuration['targetObject'];
}
// set target type for property mapping
$this->formDefinition->getProcessingRule($this->objectPrefix)->setDataType($targetClassName);
// allow all properties to be mapped
$targetObjectPropertyMappingConfiguration = $this->formDefinition->getProcessingRule($this->objectPrefix)->getPropertyMappingConfiguration();
$targetObjectPropertyMappingConfiguration->allowAllProperties();
// set property mapping mode to modification/creation depending on $this->targetObject to be set
if ($this->targetObject !== NULL) {
$targetObjectPropertyMappingConfiguration->setTypeConverterOption('TYPO3\FLOW3\Property\TypeConverter\PersistentObjectConverter', \TYPO3\FLOW3\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, TRUE);
$identity = $page1->createElement($this->objectPrefix . '.__identity', 'TYPO3.Form:HiddenField');
$identity->setDefaultValue($this->persistenceManager->getIdentifierByObject($this->targetObject));
} else {
$targetObjectPropertyMappingConfiguration->setTypeConverterOption('TYPO3\FLOW3\Property\TypeConverter\PersistentObjectConverter', \TYPO3\FLOW3\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, TRUE);
}
$this->createFormElements($page1);
// set default values and validators from the targetClassName
$this->setDefaultValues();
$this->addValidators();
// call postProcessFormValues() upon submission for further processing of the submitted values
$self = $this;
$this->formDefinition->createFinisher('TYPO3.Form:Closure')->setOption('closure', function(\TYPO3\Form\Core\Model\FinisherContext $context) use ($self) {
$self->postProcessFormValues($context->getFormValues());
});
// add more custom finishers if required
$this->addFinishers();
return $this->formDefinition;
}
/**
* Implement this method and attach form elements to the $section like:
* $name = $section->createElement($this->objectPrefix . '.name', 'TYPO3.Form:SingleLineText');
* $name->setLabel('Name');
* ...
*
* @param \TYPO3\Form\Core\Model\AbstractSection $section
* @return void
*/
abstract protected function createFormElements(\TYPO3\Form\Core\Model\AbstractSection $section);
/**
* Implement this method and return the fully qualified class name of your target object
* e.g. return 'My\Package\Domain\Model\SomeModel';
*
* @return string
*/
abstract protected function getTargetClassName();
/**
* Iterates over the form elements and sets default values if possible
*
* @return void
*/
protected function setDefaultValues() {
if ($this->targetObject === NULL) {
return;
}
$targetClassName = $this->getTargetClassName();
$object = $this->targetObject !== NULL ? $this->targetObject : new $targetClassName();
foreach ($this->formDefinition->getRenderablesRecursively() as $formElement) {
if ($formElement instanceof FormElementInterface && $formElement->getDefaultValue() === NULL) {
$elementIdentifier = substr($formElement->getIdentifier(), strlen($this->objectPrefix) + 1);
$formElement->setDefaultValue(ObjectAccess::getPropertyPath($object, $elementIdentifier));
}
}
}
/**
* Uses reflection to retrieve base validation rules for the specified targetClassName
*
* @return void
* @throws \TYPO3\FLOW3\Validation\Exception\NoSuchValidatorException
*/
protected function addValidators() {
$targetClassName = $this->getTargetClassName();
foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) {
$formElement = $this->formDefinition->getElementByIdentifier($this->objectPrefix . '.' . $classPropertyName);
if ($formElement === NULL) {
continue;
}
$validateAnnotations = $this->reflectionService->getPropertyAnnotations($targetClassName, $classPropertyName, 'TYPO3\FLOW3\Annotations\Validate');
foreach ($validateAnnotations as $validateAnnotation) {
$newValidator = $this->validatorResolver->createValidator($validateAnnotation->type, $validateAnnotation->options);
if ($newValidator === NULL) {
throw new \TYPO3\FLOW3\Validation\Exception\NoSuchValidatorException('Invalid validate annotation in ' . $this->targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validateAnnotation->type . '".', 1342536676);
}
$formElement->addValidator($newValidator);
}
}
}
/**
* Override this method if you want to post process the (already mapped) $formValue
*
* @param array $formValues
* @return void
*/
public function postProcessFormValues(array $formValues) {
}
/**
* Override this method if you want to add custom finishers
*
* @return void
*/
protected function addFinishers() {
}
}
?>
<?php
namespace Test\FormFactories;
/* *
* This script belongs to the FLOW3 package "Yeebase.Guruhelp". *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License, either version 3 *
* of the License, or (at your option) any later version. *
* *
* The TYPO3 project - inspiring people to share! *
* */
use TYPO3\FLOW3\Annotations as FLOW3;
use TYPO3\FLOW3\Error\Message;
class BookFormFactory extends \TYPO3\Form\Factory\AbstractModelFormFactory {
/**
* @FLOW3\Inject
* @var \Test\Domain\Repository\BookRepository
*/
protected $bookRepository;
/**
* @FLOW3\Inject
* @var \TYPO3\FLOW3\Mvc\FlashMessageContainer
*/
protected $flashMessageContainer;
/**
* @param \TYPO3\Form\Core\Model\AbstractSection $section
* @return void
*/
protected function createFormElements(\TYPO3\Form\Core\Model\AbstractSection $section) {
$title = $section->createElement($this->objectPrefix . '.title', 'TYPO3.Form:SingleLineText');
$title->setLabel('Title');
$price = $section->createElement($this->objectPrefix . '.price', 'TYPO3.Form:SingleLineText');
$price->setLabel('Price');
}
/**
* @return string
*/
protected function getTargetClassName() {
return 'Test\Domain\Model\Book';
}
/**
* @param array $formValues
* @return void
*/
public function postProcessFormValues(array $formValues) {
if ($this->targetObject !== NULL) {
$this->bookRepository->update($formValues[$this->objectPrefix]);
} else {
$this->bookRepository->add($formValues[$this->objectPrefix]);
}
}
/**
* @return void
*/
protected function addFinishers() {
$flashMessageFinisher = $this->formDefinition->createFinisher('TYPO3.Form:FlashMessage');
if ($this->targetObject === NULL) {
$flashMessageFinisher->setOptions(
array(
'messageBody' => 'Stream "{' . $this->objectPrefix . '.title}" has been created',
'messageTitle' => 'Stream created',
)
);
} else {
$flashMessageFinisher->setOptions(
array(
'messageBody' => 'Stream "{' . $this->objectPrefix . '.title}" has been updated',
'messageTitle' => 'Stream updated',
)
);
}
$redirectFinisher = $this->formDefinition->createFinisher('TYPO3.Form:Redirect');
$redirectFinisher->setOptions(
array(
'package' => 'Test',
'controller' => 'Standard',
'action' => 'index'
)
);
}
}
?>
{namespace form=TYPO3\Form\ViewHelpers}
<h1>Edit book "{book.title}"</h1>
<form:render factoryClass="Test\FormFactories\TestFormFactory" overrideConfiguration="{targetObject: book}" />
<f:flashMessages />
<ul>
<f:for each="{books}" as="book">
<li>
<f:link.action action="edit" arguments="{book: book}">{book.title}</f:link.action>
</li>
</f:for>
</ul>
<f:link.action action="new">Add book</f:link.action>
{namespace form=TYPO3\Form\ViewHelpers}#
<h1>Create new book</h1>
<form:render factoryClass="Test\FormFactories\BookFormFactory" />
<?php
namespace Test\Controller;
/* *
* This script belongs to the FLOW3 package "Test". *
* *
* */
use TYPO3\FLOW3\Annotations as FLOW3;
/**
* Standard controller for the Test package
*
* @FLOW3\Scope("singleton")
*/
class StandardController extends \TYPO3\FLOW3\Mvc\Controller\ActionController {
/**
* @FLOW3\Inject
* @var \Test\Domain\Repository\BookRepository
*/
protected $bookRepository;
/**
* @return void
*/
public function indexAction() {
$this->view->assign('books', $this->bookRepository->findAll());
}
/**
* @return void
*/
public function newAction() {
}
/**
* @param \Test\Domain\Model\Book $book
* @return void
*/
public function editAction(\Test\Domain\Model\Book $book) {
$this->view->assign('book', $book);
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment