I have a Model:
class SearchOfferFacet
{
/**
* #var string
*/
protected $id;
/**
* #var array
*/
protected $country;
//getters ans setters....
And I have a FormType
...
$builder
->add('country', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices_as_values' => true,
'choices' => array('US' => "USA", "IT" => "Italia"),
))
When I create the form in my controller with
$searchOfferFacet = new SearchOfferFacet();
$searchOfferFacet->setCountry(
array(
'US' => 'US',
'MV' => 'MV'
)
);
$form = $this->createForm(SearchOfferFacetType::class, $searchOfferFacet, array('method' => 'POST'));
I get the following Exception:
An exception has been thrown during the rendering of a template ("Notice: Array to string conversion") in form_div_layout.html.twig at line 13.
But when I change the variable in my model to String, and I set a string as Country, for example "US", then the exception appears:
Expected an array.
In my TWIG there is nothing special, just:
{{ form_widget(form.country) }}
Does anyone has an idea what is wrong?
Can you try setting 'multiple' => true,? My suspicion here is that you the member is an array, but since you set 'multiple' => false,, it only needs to save a string.
On the other hand, you can then also just do
/**
* #var string
*/
protected $country;
and the setCountry(...) function should only save a string.
Related
final class SomeAdmin extends AbstractAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('sending_error', null, [
'label' => 'some label;',
]);
}
}
// ...
class Entity
{
/**
* #var bool|null
*
* #ORM\Column(type="boolean", nullable=true)
*/
private $sending_error;
// ...
}
This code generates a filter with two values: yes / no. "yes" option will return rows with value "true" in field, "false" option of the filter will return rows with value "false" in db field, but how to include rows with "null" value in "no" filter option?
You can add custom choices for your filter if you set them in your add() options.
For symfony versions before 4.3 and sonata-admin-bundle 3 you can do it like this:
$datagridMapper
->add('sending_error',
'doctrine_orm_string',
array(),
'choice',
array('choices' => array('m' => 'Male', 'f' => 'Female')
)
);
And for the newest versions (this i have test locally and works for me)
->add('sending_error', null, ['label' => 'some label'], ChoiceType::class, [
'choices' => ['True' => True, 'False' =>False,'Empty'=>null]])
I have an Announcement Entity that where a EditAnnouncementType form is mapped to. I have two other CheckBoxType forms that automatically update their respective fields, but the ChoiceType form isn't working.
$builder
->add('edit', SubmitType::class,
array
(
'label' => 'Save changes',
'attr' => ['class' => 'btn btn-primary']
))
->add('type', ChoiceType::class,
[
'choices' => [
'info_type' => 1,
'star_type' => 2,
'alert_type' => 3,
'lightbulb_type' => 3,
'event_type' => 4,
'statement_type' => 5,
'cat_type' => 6,
'hands_type' => 7
],
'mapped' => true,
'expanded' => true,
'required' => true,
])
The Announcement entity and type field
class Announcement
{
/**
* #var int
*/
private $type;
/**
* #return string
*/
public function getType(): string
{
return $this->type;
}
/**
* #param int $type
*
* #return $this
*/
public function setType(int $type)
{
$this->type = $type;
return $this;
}
My suspicion would be that Symfony somehow strict checks the value (using ===).
And since your getter returns a string, the mapping doesn't happen properly.
You should try fixing your getter:
/**
* #return int
*/
public function getType(): int
{
return $this->type;
}
Also mind that you might have a problem in your choice array:
// be careful: those two have the same value
'alert_type' => 3,
'lightbulb_type' => 3,
This would surely cause an issue to Symfony, especially if it uses array_values in order to select the right choice out of the value of your entity.
I'm really confused about my Form Filter.
My Test-Project contains 2 Models.
class Category extends AbstractEntity
{
use Nameable; // just property name and getter and setter
/**
* #var boolean
* #ORM\Column(name="issue", type="boolean")
*/
private $issue;
/**
* #var Collection|ArrayCollection|Entry[]
*
* #ORM\OneToMany(targetEntity="CashJournal\Model\Entry", mappedBy="category", fetch="EAGER", orphanRemoval=true, cascade={"persist", "remove"})
*/
private $entries;
}
the entry
class Entry extends AbstractEntity
{
use Nameable;
/**
* #var null|float
*
* #ORM\Column(name="amount", type="decimal")
*/
private $amount;
/**
* #var null|Category
*
* #ORM\ManyToOne(targetEntity="CashJournal\Model\Category", inversedBy="entries", fetch="EAGER")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
*/
protected $category;
/**
* #var null|DateTime
*
* #ORM\Column(name="date_of_entry", type="datetime")
*/
private $dateOfEntry;
}
And if someone needed the AbstractEntity
abstract class AbstractEntity implements EntityInterface
{
/**
* #var int
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
Every Category can have many Entries. I'm using Doctrine for this relation. And this works fine.
I have a Form based on this FieldSet:
$this->add([
'name' => 'id',
'type' => Hidden::class
]);
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => 'Name'
]
]);
$this->add([
'name' => 'amount',
'type' => Number::class,
'options' => [
'label' => 'Summe'
]
]);
$this->add([
'name' => 'date_of_entry',
'type' => Date::class,
'options' => [
'label' => 'Datum'
]
]);
$this->add([
'name' => 'category',
'type' => ObjectSelect::class,
'options' => [
'target_class' => Category::class,
]
]);
So my Form displays a dropdown with my categories. Yeah fine.
To load the Category for my Entry Entity i use a filter.
$this->add([
'name' => 'category',
'required' => true,
'filters' => [
[
'name' => Callback::class,
'options' => [
'callback' => [$this, 'loadCategory']
]
]
]
]);
And the callback:
public function loadCategory(string $categoryId)
{
return $this->mapper->find($categoryId);
}
The mapper loads the category fine. great. But the form is invalid because:
Object of class CashJournal\Model\Category could not be converted to int
Ok, so i'm removing the Filter, but now it failed to set the attributes to the Entry Entity, because the setter needs a Category. The Form error says:
The input is not a valid step
In Symfony i can create a ParamConverter, which converts the category_id to an valid Category Entity.
Question
How i can use the filter as my ParamConver?
Update
Also when i cast the category_id to int, i will get the error from the form.
Update 2
I changed my FieldSet to:
class EntryFieldSet extends Fieldset implements ObjectManagerAwareInterface
{
use ObjectManagerTrait;
/**
* {#inheritDoc}
*/
public function init()
{
$this->add([
'name' => 'id',
'type' => Hidden::class
]);
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => 'Name'
]
]);
$this->add([
'name' => 'amount',
'type' => Number::class,
'options' => [
'label' => 'Summe'
]
]);
$this->add([
'name' => 'date_of_entry',
'type' => Date::class,
'options' => [
'label' => 'Datum'
]
]);
$this->add([
'name' => 'category',
'required' => false,
'type' => ObjectSelect::class,
'options' => [
'target_class' => Category::class,
'object_manager' => $this->getObjectManager(),
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label_generator' => function ($targetEntity) {
return $targetEntity->getName();
},
]
]);
parent::init();
}
}
But this will be quit with the error message:
Entry::setDateOfEntry() must be an instance of DateTime, string given
Have you checked the documentation for ObjectSelect? You appear to be missing a few options, namely which hydrator (EntityManager) and identifying property (id) to use. Have a look here.
Example:
$this->add([
'type' => ObjectSelect::class,
'name' => 'category', // Name of property, 'category' in your question
'options' => [
'object_manager' => $this->getObjectManager(), // Make sure you provided the EntityManager to this Fieldset/Form
'target_class' => Category::class, // Entity to target
'property' => 'id', // Identifying property
],
]);
To validate selected Element, add in your InputFilter:
$this->add([
'name' => 'category',
'required' => true,
]);
No more is needed for the InputFilter. A Category already exist and as such has been validated before. So, you should just be able to select it.
You'd only need additional filters/validators if you have special requirements, for example: "A Category may only be used once in Entries", making it so that you need to use a NoObjectExists validator. But that does not seem to be the case here.
UPDATE BASED ON COMMENTS & PAST QUESTIONS
I think you're over complicating a lot of things in what you're trying to do. It seems you want to simply populate a Form before you load it client-side. On receiving a POST (from client) you wish to put the received data in the Form, validate it and store it. Correct?
Based on that, please find a complete controller for User that I have in one of my projects. Hope you find it helpful. Providing it because updates are veering away from your original question and this might help you out.
I've removed some additional checking and error throwing, but otherwise is in complete working fashion.
(Please note that I'm using my own abstract controller, make sure to replace it with your own and/or recreate and match requirements)
I've also placed additional comments throughout this code to help you out
<?php
namespace User\Controller\User;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\ORMException;
use Exception;
use Keet\Mvc\Controller\AbstractDoctrineActionController;
use User\Entity\User;
use User\Form\UserForm;
use Zend\Http\Request;
use Zend\Http\Response;
class EditController extends AbstractDoctrineActionController
{
/**
* #var UserForm
*/
protected $userEditForm; // Provide this
public function __construct(ObjectManager $objectManager, UserForm $userEditForm)
{
parent::__construct($objectManager); // Require this in this class or your own abstract class
$this->setUserEditForm($userEditForm);
}
/**
* #return array|Response
* #throws ORMException|Exception
*/
public function editAction()
{
$id = $this->params()->fromRoute('id', null);
// check if id set -> else error/redirect
/** #var User $entity */
$entity = $this->getObjectManager()->getRepository(User::class)->find($id);
// check if entity -> else error/redirect
/** #var UserForm $form */
$form = $this->getUserEditForm(); // GET THE FORM
$form->bind($entity); // Bind the Entity (object) on the Form
// Only go into the belof if() on POST, else return Form. Above the data is set on the Form, so good to go (pre-filled with existing data)
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost()); // Set received POST data on Form
if ($form->isValid()) { // Validates Form. This also updates the Entity (object) with the received POST data
/** #var User $user */
$user = $form->getObject(); // Gets updated Entity (User object)
$this->getObjectManager()->persist($user); // Persist it
try {
$this->getObjectManager()->flush(); // Store in DB
} catch (Exception $e) {
throw new Exception('Could not save. Error was thrown, details: ', $e->getMessage());
}
return $this->redirectToRoute('users/view', ['id' => $user->getId()]);
}
}
// Returns the Form with bound Entity (object).
// Print magically in view with `<?= $this->form($form) ?>` (prints whole Form!!!)
return [
'form' => $form,
];
}
/**
* #return UserForm
*/
public function getUserEditForm() : UserForm
{
return $this->userEditForm;
}
/**
* #param UserForm $userEditForm
*
* #return EditController
*/
public function setUserEditForm(UserForm $userEditForm) : EditController
{
$this->userEditForm = $userEditForm;
return $this;
}
}
Hope that helps...
I currently have a working form in Symfony where I have a list of companies with checkboxes next to each company name. This is so you can check off which company is assigned to each user. The checkbox currently shows the accountID but it would also be helpful to have the entity field 'name' as well. Can you build a property with two entity fields? Here is my form in my controller:
->add('companies', 'entity', array(
'label' => 'Company',
'class' => 'Default\Bundle\Entity\Customer',
'property' => 'accountId', //This puts the company id next to the check box
'multiple' => true,
'expanded' => true,
'query_builder' => function ($repository)
{
return $repository->createQueryBuilder('c')->orderBy('c.accountId', 'ASC');
},))
->add('Save', 'submit')
->getForm();
This is what I am trying to do:
->add('companies', 'entity', array(
'label' => 'Company',
'class' => 'Default\Bundle\Entity\Customer',
'property' => 'accountId' + 'name', // I want to combine my entity fields here
'multiple' => true,
'expanded' => true,
'query_builder' => function ($repository)
here is the entity just for reference
class Customer
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Assert\NotBlank(message="Please enter a Customer ID.")
* #Assert\Length(max="32")
* #ORM\Column(type="string", length=32)
* #var string
*/
protected $accountId;
/**
* #Assert\NotBlank(message="Please enter a company name.")
* #Assert\Length(max="60")
* #ORM\Column(type="string", length=60)
* #var string
*/
protected $name;
And one last time... I want to go from this:
To this:
Create a simple getter and use that as the property, eg:
public function getNamePlusAccountId()
{
return $this->name." (".$this->accountId.")";
}
and use 'property' => 'namePlusAccountId' in your form.
If you only need to change the label but would like to keep the form field value then http://symfony.com/doc/current/reference/forms/types/entity.html#choice-label probably what are you looking for
How to implement a group of elements in the drop-down list ObjectSelect 'optgroup_identifier'
Form\CategoryForm.php
$this->add([
'type' => ObjectSelect::class,
'name' => 'category',
'options' => [
'label' => 'Категория',
'object_manager' => $this->getObjectManager(),
'target_class' => Category::class,
'property' => 'name',
'optgroup_identifier' => '???',
'optgroup_default' => 'Главная',
'empty_option' => '== Категория ==',
'is_method' => true,
'find_method' => [
'name' => 'findAllChildCategories',
'params' => [
],
],
],
]);
Category Table is relevant Self-referencing
Entity\Category.php
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="Application\Entity\Category", mappedBy="parent", cascade={"remove"})
*/
private $children;
/**
* #var \Application\Entity\Category
*
* #ORM\ManyToOne(targetEntity="Application\Entity\Category", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent", referencedColumnName="id", nullable=true)
* })
*/
private $parent;
Group name must be the parent category
$category->getParent()->getName()
Thankfully for this case, Doctrine does not do any queries to get groupings; it does it internally. optgroup_identifier is just name of a getter it uses to get group names, therefore that getter can return anything you want.
In Entity\Category, add a method dedicated to returning parent name of a category. Ensure it does not coincide with any fields so Doctrine does not return a whole object proxy into a form. For example:
public function getParentName() {
if(!$this->parent) return '';
return $this->parent->getName();
}
Since root categories will not have a parent, $this->parent will be null. Look out for that case to avoid script crashing and return empty string as a designation for it.
Then, put this getter name in optgroup_identifier of the form. Final result will be as in screenshot with sample data.