How to properly hydrate and extract Doctrine Entities from Zend Forms - doctrine

I'm just starting out with Doctrine and was rewriting some code to use Doctrine entities in some Forms.
I have an Entity Business which has some 1:n relations with addresses, employees, emails etc. the Setup is really basic and working fine.
To add new Businesses i created a BusinessForm and Fieldsets for each of my entities. Here the constructor of the form:
public function __construct($scenario='create', $entityManager = null) {
parent::__construct('business_form');
$this->scenario = $scenario;
$this->entityManager = $entityManager;
$this->setAttribute('method', 'post');
$businessFieldset = new BusinessFieldset($this->entityManager);
$businessFieldset->setUseAsBaseFieldset(true);
$this->add($businessFieldset);
$hydrator = new DoctrineHydrator($this->entityManager, new Business());
$this->setHydrator($hydrator);
$this->addElements();
$this->addInputFilter();
}
addElements just adds a Submit and CSRF input.
And here the Controller action:
public function addAction(){
$form = new BusinessForm('create', $this->entityManager);
if ($this->getRequest()->isPost()) {
$data = $this->params()->fromPost();
$form->setData($data);
if($form->isValid()) {
// save Object
return $this->redirect()->toRoute('subcontractor', ['action'=>'index']);
}
}
return new ViewModel([
'form' => $form
]);
}
The form validates and i can get the Data from the form with $form->getData(). But i cant figure out how to get a populated Object from the form using the form's hydrator. When I use setObject(new Business()) at the start of the controller i get an error while $form->isValid() is running :
Zend\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()
Isnt that the wrong hydrator being called ?
If i dont setObject() but instead use $form->bind(new Business()) after the validation i get an empty Object from $form->getObject(). If i get the data and hydrate a new Object with the form's hydrator like so : $form->getHydrator()->hydrate($data['business], new Business()) i do get the populated Object i was expecting. (Business being the name of the base fieldset)
So my question is, how to i get the result of the last call from just using $form->getObject() after the validation?
[EDIT]
The Problem seems to be with the Collections of Fieldsets used as sub-fieldsets in the businessfieldset. If i validate the form without using the collections i do get the expected Business Object from $form->getData()
Here an example how i add the collection (in the business fieldset):
$this->add([
'name' => 'emails',
'type' => 'Zend\Form\Element\Collection',
'attributes' => [
'id' => 'business_emails',
],
'options' => [
'label' => 'Emails',
'count' => 1,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'target_element' => [
'type' => 'LwsSubcontractor\Form\EmailFieldset',
],
'target_class' => 'LwsSubcontractor\Entity\Email'
],
]);
and here the EmailFieldset:
public function __construct() {
parent::__construct('email');
$this->setObject(new Email());
$this->addElements();
}
protected function addElements() {
$this->add([
'name' => 'email',
'type' => 'Zend\Form\Element\Email',
'attributes' => [
'placeholder' => 'E-Mail (z.B. email#muster-email.de)',
'class' => 'form-control',
'required' => true,
'size' => 50,
],
'options' => [
'label' => 'Email',
],
]);
}
}
If using the Collections i get the Error message from above. So after adding a hydrator to each Fieldset i was fine.
Although i was under the impression that setting the hydrator for the form would result in the used fieldsets to inherit that hydrator.Or was this because of using the fieldsets as collections and not directly ?

You have to add the hydrator to all your fieldsets, personally I use DoctrineModule\Stdlib\Hydrator\DoctrineObject for doctrine entities.
I would also look at using the init() method to initialize your forms and add elements then register and retrieve your form and fieldsets through the FormElementManager, $serviceLocator->get('FormElementManager')->get(yourFieldsetorForm::class). The form can than be injected into your controller.
I hope this helps.

Related

Add a custom item to eloquent collection

I am using Laravel 7.
I have Category model. I sent categories as API with laravel resources. But now I want to add "all" value to categories.
Controller:
'categories' => CategoryResource::collection(Category::all()->push([
'id' => 0,
'name' => "All",
'subcategories' => []
]))
Resource:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'subcategories' => SubcategryResource::collection($this->subcategories)
];
}
Also, I wanted to add this custom value to the beginning of the collection.
But I am getting this error:
Trying to get property 'id' of non-object
Please, help me. How can I solve my problem?
Try replacing $this->id with $this['id']
If that works your query is returning an array not an object.
The problem here is that you're trying to add an item to the collection which is not a category model. $this in the resource reverts to the given model. However, you pass in an array so there is no model.
So try this instead
$allCategory = new Category(['id' => 0, 'name' => 'All']);
'categories' => CategoryResource::collection(Category::all()->prepend($allCategory));
I got these from: https://laracasts.com/discuss/channels/eloquent/add-a-custom-value-to-eloquent-collection

Validate Select2 in Yii2 via AJAX

I have Yii2 application which uses the Kartik plugin to initialize Select2 dropdowns in forms.
I have one particular Select2 which uses AJAX call to get the data for the drop down options.
<?=
$form->field($model, 'court_house_id', ['enableAjaxValidation' => true, 'selectors' => ['input' => '#' . $id . "-court-house"],'template' => FormHelper::GenerateFieldTemplate([6])])
->widget(Select2::classname(), [
'options' => ['id' => $id . "-court-house", 'placeholder' => Yii::t('app', 'Search court house...')],
'hashVarLoadPosition' => \yii\web\View::POS_READY,
'pluginOptions' => [
'dropdownParent' => new JsExpression("$('#$modalWindowId')"),
'allowClear' => true,
'minimumInputLength' => 2,
'language' => [
'errorLoading' => new JsExpression("function () { return '" . Yii::t('app', 'Search...') . "'; }"),
],
'ajax' => [
'url' => app\components\UrlMaker::link('data/court-house-list'),
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }')
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(courthouse) { return courthouse.text; }'),
'templateSelection' => new JsExpression('function (courthouse) { return courthouse.text;}'),
]])
->label(Yii::t('app', 'Court House'), ['class' => FormHelper::GenerateLabelClassTemplate([3])]);
?>
Intentionally pasting all of the code, although most of it is irrelevant I would assume.
I have this loaded in multiple dynamically created forms thus all the strange ids and selectors. However, the form has different dropdown which controls whether some of the fields are shown (and required) or not. This particular field above is only shown in one of the scenarios which all the other variations of the form do not have it. So the model has the following validation:
[['court_house_id', 'staff'], 'required', 'on' => self::SCENARIO_ONE],
By the way staff is just a regular text field and everything works for it.
In order to change the scenario, I have the following line in the view with the form:
<?php $model->scenario = \app\models\MyModel::SCENARIO_ONE; ?>
The problem is that when I submit the form empty, the staff field gets marked in red as invalid but the court house is marked in green as valid although it is empty.
If I go into the model and remove the 'on' => self::SCENARIO_ONE part then everything works as expected - on empty submit the court house field also lights up in red but that would be a problem for the rest of my scenarios where this field is not needed.
Any ideas what might be causing the problem and how to resolve it?
Try to set the scenario in controller before calling save() method, for example
$model = new MyModel(['scenario' => MyModel::SCENARIO_ONE])

Create form in Controller using Sonata field type

In Symfony admin, I have a form, where the second field type depends on the ChoiceField value selected. The second field can be of Symfony Url field type or sonata provided sonata_type_model_list field type.
I have created an ajax request to My Bundle Controller to return the form, which contains the needed field.
> /src/MyBundle/Controller/MyController.php
namespace MyBundle\Controller
use Sonata\AdminBundle\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Sonata\AdminBundle\Form\FormMapper;
class MyController extends CRUDController
{
public function getFieldAction()
{
//getting the value of choice field
$type = $this->get('request')->get('type');
//sonata.admin.reference is a service name of ReferenceBundle admin class
$fieldDescription = $this->admin->getModelManager()
->getNewFieldDescriptionInstance($this->admin->getClass(), 'reference');
$fieldDescription->setAssociationAdmin($this->container->get('sonata.admin.reference'));
$fieldDescription->setAdmin($this->admin);
$fieldDescription->setAssociationMapping(array(
'fieldName' => 'reference',
'type' => ClassMetadataInfo::ONE_TO_MANY,
));
// Getting form mapper in controller:
$contractor = $this->container->get('sonata.admin.builder.orm_form');
$mapper = new FormMapper($contractor, $this->admin->getFormBuilder(), $this->admin);
$form_mapper = $mapper->add('reference', 'sonata_type_model_list', array(
'translation_domain' => 'ReferenceBundle',
'sonata_field_description' => $fieldDescription,
'class' => $this->container->get('sonata.admin.reference')->getClass(),
'model_manager' => $this->container->get('sonata.admin.reference')->getModelManager(),
'label' => 'Reference',
'required' => false,
));
//#ToDo build $form from $form_mapper
return $this->render('MyBundle:Form:field.view.html.twig', array(
'form' => $form->createView(),
));
}
}
I cannot find any method in Sonata\AdminBundle\Form\FormMapper class to build a form (it seems to be possible with create() method, but it only works with common Symfony field types, not Sonata form field types, which are commonly generated in Block or Admin classes).
Is it possible to use Sonata\AdminBundle\Form\FormMapper in the Controller to build a form?
Or is there another way I can build a form with Sonata form field types in the Controller?
You should not use a controller, rather a Sonata Admin Service.
Sonata provides you with the 'sonata_type_choice_field_mask' type which allows you to change the fields displayed on the form dynamically depending on the value of this 'sonata_type_choice_field_mask' input.
Here is the doc where you can find everything about sonata types and the choice field mask.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('reference', 'sonata_type_choice_field_mask', array(
'choices' => array(
//The list of available 'Reference' here
'choice1',
'choice2'
),
'map' => array(
//What you want to display depending of the selected option
'choice1' => array(
// List of the fields displayed if choice 1 is selected
'field1', 'field3'
),
'choice2' => array(
// List of the fields displayed if choice 2 is selected
'field2', 'field3'
)
),
'placeholder' => 'Choose an option',
'required' => false
))
->add('field1', 'sonata_type_model_list', array(/* Options goes here */))
->add('field2', 'url', array(/* Options goes here */))
->add('field3')
;
}
It looks like you have access to the admin which directly handles building the form with Sonata\AdminBundle\Admin\AbstractAdmin::getForm.
$form = $this->admin->getForm();

ZF2 using inputFilter

I wonder what I've made wrong, I want to save in db some values whats not come form POST or GET:
public function saveAction()
{
$wikiTable = $this->getServiceLocator()->get('WikiTable');
$data = array('source' => $someVal);
$form = new WikiForm();
$inputFilter = new \MyApp\Form\WikiFilter();
$form->setInputFilter($inputFilter);
$form->setData($data);
$this->saveWiki($form->getData());
//$this->saveWiki($data);
}
WikiFilter:
$this->add(
array(
'name' => 'source',
'required' => false,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
)
)
);
And Form:
$this->add(array(
'name' => 'source',
'type' => 'Zend\Form\Element\Hidden',
'options' => array(
'label' => 'source',
)
));
In response I recive error:
Zend\Form\Form::getData cannot return data as validation has not yet
occurred
After this line:
$form->setData($data);
You need to put the rest of your code into something like this:
if($form->isValid()){
$this->saveWiki($form->getData());
}
Otherwise your form isn't validated and you won't get any data back from it by calling $form->getData()
So whenever you work with a form (not matter if the data come from a POST request or not) make sure to call the function isValid() on the form variable because otherwise you won't get the data back and you will get the error you wrote before

ZF2 + Duplicate Form Validation on composite key

I have a Form having primary key on two fields (gid, bid). I need to add validation to block duplicate entries into database.
I have checked with ZF2 Solution for this . http://framework.zend.com/manual/2.2/en/modules/zend.validator.db.html#excluding-records . While this approach of handling composite keys is not look the ideal way, But still I am trying it because it look like only buil-in way. Now it require me to provide second field's value (value option in exclude), which is again a problem. As I am trying it
$inputFilter->add(array(
'name' => 'gid',
'required' => true,
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'messages' => array(
'isEmpty' => 'required'
),
),
),
array (
'name' => 'Zend\Validator\Db\NoRecordExists',
'options' => array (
'table' => 'gtable',
'field' => 'gid',
'adapter' => $this->dbAdapter,
'messages' => array(
\Zend\Validator\Db\NoRecordExists::ERROR_RECORD_FOUND => 'The specified key already exists in database'
),
'exclude' => array(
'field' => 'bid',
'value' => [?],
),
)
),
)
));
How do I get this value, As Form is absolute separate Class/File than controller where I have the submitted form values. Is some better architecture solution of this problem exists Or Some hack to pass submitted field value to Form Class is only solution ?
Note : I am not in favor of Build My Validation Plugin for this task as short time is constraint for functionality.
You can do all the job in your form. To achieve that, you could define your forms as factories in your module Module.php.
Module.php
use MyNamespace\MyForm;
//NOTE THAT THE SERVICE MANAGER IS INJECTED. YOUR FORM COULD RECEIVE IT THROUGH THE CONSTRUCTOR
public function getServiceConfig()
{
return array(
'factories' => array(
'my_form' => function( $sm ) {
$form = new MyForm( $sm );
return $form;
},
),
);
}
When you want to use the form is as easy as use this code in your controller:
class MyController extends AbstractActionController
{
public function createAction() {
$form = $this->getServiceLocator()->get( 'my_form' ) );
(...)
}
}
And your MyForm.php
use Zend\Form\Form;
class MyForm extends Form
{
public $serviceManager, $request, $postData;
public function __construct( $serviceManager ) {
parent::__construct( null );
$this->serviceManager = $serviceManager;
$this->request = $serviceManager->get( 'Application')->getMvcEvent()->getRequest();
$this->postData = get_object_vars( $this->request->getPost() );
}
}
This way you can get advantage of the Service Manager within your form. And the public postData, where you'll find the bid value you're looking for to build your NoRecordExists filter.
You could add the parameters to the getInputFilter, like this :
getInputFilter($gid, $bid)
And then on the controller, when you set the filter you pass the 2 parameters, and then just check as $form->isValid(); ...
Alternative try this:
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => 'gtable',
'field' => 'gid',
'adapter' => $this->dbAdapter,
),
),
I'm unsure on your use case. If you were to add a database entry the primary keys for that table would not be known until you insert anyway - If you have foreign key constraints you could handle the exception from the database.
I am not in favor of Build My Validation Plugin for this task
The validator is also not designed to validate multiple fields as they are attached to a form element on a 1-1 basis. You will therefore need to create your own.
The below example has NOT been tested, so take it as an example of the approach rather than working code.
The key bit is the isValid method.
namespace MyModule\Validator\Db;
use Zend\Validator\Db\NoRecordExists;
class CompositeNoRecordExists extends NoRecordExists
{
protected $field2;
protected $field2Value;
public function __construct($options = null)
{
parent::__construct($options);
if (array_key_exists('field2', $options)) {
$this->setField2($options['field2']);
} else {
throw new \BadMethodCallException('Missing field2 option!');
}
}
protected function setField2Value(array $context)
{
if (! isset($context[$this->field2])) {
throw new \BadMethodCallException('Unable to find value for field 2');
}
$this->field2Value = $context[$this->field2];
}
public function isValid($value)
{
// The isValid() method is actually given a 2nd argument called $context
// Which is injected by the inputFilter, via the input and into the validator chain
// $context contains all of RAW form element values, keyed by thier element name.
// Unfortunately due to the ValidatorInterface you are unable to add this to the method
// signature. So you will need to be 'creative':
$args = func_get_args();
if (isset($args[1]) && is_array($args[1])) {
$this->setField2Value($args[1]);
} else {
throw new \BadMethodCallException('Missing validator context');
}
return parent::isValid($value);
}
public function getSelect()
{
$select = parent::getSelect();
$select->where->equalTo($this->field2, $this->field2Value);
return $select;
}
}
Then all you would need to do is update the validator config, adding the field2 field name.
array (
'name' => 'MyModule\Validator\Db\CompositeNoRecordExists',
'options' => array (
'table' => 'gtable',
'field' => 'gid',
'field2' => 'bid',
'adapter' => $this->dbAdapter,
'messages' => array(
\Zend\Validator\Db\NoRecordExists::ERROR_RECORD_FOUND => 'The specified key already exists in database'
),
)
),

Resources