Creating a custom Customer attribute through module upgrade (UpgradeData.php) - php

As title says, i'm having troubles at creating new customers attribute when upgrading one of my modules.
I've created the UpgradeData.php file under /app/code/vendor/modulename/Setup/UpgradeData.php with the current code:
namespace Ucs\CustomerAttribute\Setup;
use Magento\Customer\Model\Customer;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Customer\Setup\CustomerSetupFactory;
class UpgradeData implements UpgradeDataInterface{
private $customerSetupFactory;
public function __construct(
CustomerSetupFactory $customerSetupFactory
) {
$this->customerSetupFactory = $customerSetupFactory;
}
/**
* {#inheritdoc}
*/
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context){
$setup->startSetup();
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
if (version_compare($context->getVersion(), '1.0.6') < 0) {
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'nome_azienda', [
'type' => 'varchar',
'label' => 'Nome azienda',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 333,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'nome_azienda')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]]);
$attribute->save();
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'codice_univoco', [
'type' => 'varchar',
'label' => 'Codice Univoco',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 333,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'codice_univoco')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]]);
$attribute->save();
}
}
}
in short, it needs to create 2 new text (varchar) attributes. My module.xml has
setup_version="1.0.5" schema_version="1.0.5" so it should enter the version_compare condition and create the attribute, but, after running php bin/magento setup:upgrade it doesn't work. Even by removing that "if" condition no attributes are created. If i check in the setup_module table, the setup_version and schema_version change correctly with the version in the module.xml. It looks like for some reason the UpgradeData.php does not get executed at all (even trying to log a string into a log file doesn't write anything). Other log files under ..../var/log/ show no errors or warnings. With this module i have already created OTHER custom Customer attributes with the installData.php file with no issues at all.
I have no idea what to do, can't really understand what i'm doing wrong

You can check it the below links:
https://magento.stackexchange.com/questions/254790/upgradedata-to-add-a-custom-customer-attribute-is-not-working-in-magento2
https://github.com/mundipagg/magento2/blob/master/Setup/UpgradeData.php

Related

ZF2 multi-select form preselect values ManyToMany

I can't get to preselect values in a multiselect form element representing a many to many relation.
In my model $admin I have the proper data : an ArrayCollection containing the correct CampsTypes but in the form I can't get the multiselect to preselect the proper options.
Admins model
/**
* #var ArrayCollection CampsTypes $campstypes
*
* #ORM\ManyToMany(targetEntity="CampsTypes", inversedBy="admins", cascade={"persist"})
* #ORM\JoinTable(name="campstypes_admins",
* joinColumns={#ORM\JoinColumn(name="admins_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="campstypes_id", referencedColumnName="id")}
* )
*/
private $campstypes;
CampsType model
/**
* #var ArrayCollection Admins $admins
*
* #ORM\ManyToMany(targetEntity="Admins", mappedBy="campstypes", cascade={"persist"})
*/
private $admins;
Then I define my form select element as follow
[
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'campTypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
And finally here is my action to receive the form
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campTypes'])) {
$ids = $form->get('campTypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampTypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
I'm getting out of solution to try.
Any idea what I am doing wrong ?
Have you read this ? I have the feeling you're looking for the ObjectMultiCheckbox instead of the ObjectSelect Form Element.
Examples from my own code
Usage for a single select (use case: set/change a default currency for some other entity)
$this->add([
'type' => ObjectSelect::class,
'required' => true,
'name' => 'defaultCurrency',
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Currency::class,
'property' => 'id',
// Use these commented lines if you wish to use a Repository function ('name' => 'repositoryFunctionName')
// 'is_method' => true,
// 'find_method' => [
// 'name' => 'getEnabledCurrencies',
// ],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Default currency'),
'label_attributes' => [
'class' => '',
'title' => _('Default currency'),
],
'label_generator' => function ($targetEntity) {
/** #var Currency $targetEntity */
return $targetEntity->getName(); // Generates option text based on name property of Entity (Currency in this case)
},
],
]);
Usage for multiple select (Use case: add/remove (multiple) roles to/from user)
$this->add([
'name' => 'roles',
'required' => false,
'type' => ObjectMultiCheckbox::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Role::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Roles'),
'label_generator' => function ($targetEntity) {
/** #var Role $targetEntity */
return $targetEntity->getName();
},
],
]);
As a side note: you should really use factories more. I see you using the ServiceLocator throughout your class code, you can avoid that by injecting your needs via a Factory.
If you need more information, I suggest you have a look at a bunch of my past questions as well. I had quite a few, starting out similar to what you're looking for. Managed to figure quite a few of them out on my own and have tried to describe the solutions in depth.
So I made it work !
basically my main problem is that I was not using proper Doctrine naming convention for the fields name of the form.
So I had to reverse engineer my DB into doctrine entities to compare with what I had done to understand where it was not working.
And also to set the model in both end (admins in camptype and camptypse in admin) for the whole shabang to work.
Here are the working classes :
Admin form:
[
'type' => ObjectSelect::class,
'name' => 'campstypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
Admin Controller:
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campstypes'])) {
$ids = $form->get('campstypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampstypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
So by renaming properly the fields of my form and models and setting the data in both end model of the relation I got it to work.

ZendFramework 2 - removing InputFilter causes misbehavior in custom filter

I have an registration form User\UserForm which contains a fieldset User\UserFieldset. In User\UserFieldset I put an field called "passwordVerify" which should be identical with another field in the fieldset called "password".
This works fine.
However, if an admin wants to modify an user account within the Admin\UserForm, which also contains the User\UserFieldset, the field "passwordVerify" in the fieldset User\UserFieldset should be removed. Therefore I call the following within the Admin\UserForm:
$this->get('user')->remove('passwordVerify');
$this->getInputFilter()->get('user')->remove('passwordVerify')
As expected, the form lacks the field "passwordVerify" now.
If I save the form after editing some stuff, my custom filter "PasswordFilter" cannot retrieve the bound object of the fieldset anymore ($this->getOption('object'); returns an User-object) - but all properties of the bound object are nulled. If I use the Admin\UserForm without removing "passwordVerify"-field and "passwordVerify"-inputfilter everything works fine and the bound object is passed to "PasswordFilter" with populated properties (in respect of values, inserted by the user in the Admin\UserForm). The line which breaks everything is $this->getInputFilter()->get('user')->remove('passwordVerify'). So this leads to my assumption, that by removing an inputfilter, the hydrated object gets somehow nulled / emptied. Below are my some excerpts of my code, if needed I can provide more information about factories, etc.
Admin\UserForm:
class UserForm extends Form
{
/**
* #var EntityManager
*/
protected $entityManager = null;
/**
* #var Translator
*/
protected $translator = null;
public function __construct(EntityManager $entityManager, Translator $translator)
{
$this->entityManager = $entityManager;
$this->translator = $translator;
parent::__construct("userForm");
$this->setHydrator(new DoctrineHydrator($entityManager));
}
public function init()
{
// Adding UserFieldset
$this->add(array(
'name' => 'user',
'type' => \User\Form\UserFieldset::class,
'options' => array(
'use_as_base_fieldset' => true,
),
));
$this->get('user')->remove('passwordVerify');
$this->getInputFilter()->get('user')->remove('passwordVerify');
$this->add(array(
'type' => 'Zend\Form\Element\Csrf',
'name' => 'csrf',
));
$this->add(array(
'type' => 'submit',
'name' => 'submit',
'options' => array(
'label' => $this->translator->translate('Btn.submit.user', 'Form')
),
));
}
}
User\UserFieldset:
class UserFieldset extends Fieldset implements InputFilterProviderInterface
{
/**
* #var EntityManager
*/
protected $entityManager = null;
/**
* #var Translator
*/
protected $translator = null;
public function __construct(EntityManager $entityManager, Translator $translator)
{
$this->entityManager = $entityManager;
$this->translator = $translator;
parent::__construct("userFieldset");
$this->setHydrator(new DoctrineHydrator($entityManager))->setObject(new User());
}
public function init()
{
$this->add(array(
'type' => 'text',
'name' => 'firstName',
'options' => array(
'label' => $this->translator->translate('label.firstName', 'Form'),
'label_attributes' => array(
'class' => 'col-sm-3',
),
'column-size' => 'sm-5',
),
'attributes' => array(
'id' => 'firstName',
),
));
/* ... */
$this->add(array(
'type' => 'text',
'name' => 'password',
'options' => array(
'label' => $this->translator->translate('label.password', 'Form'),
'label_attributes' => array(
'class' => 'col-sm-3',
),
'column-size' => 'sm-5',
),
'attributes' => array(
'id' => 'password',
),
));
$this->add(array(
'type' => 'password',
'name' => 'passwordVerify',
'options' => array(
'label_attributes' => array(
'class' => 'col-sm-3 control-label'
),
'label' => $this->translator->translate('label.verifyPassword', 'Form'),
'column-size' => 'sm-8',
),
'attributes' => array(
'class' => 'form-control',
'id' => 'password'),
));
/* ... */
// Adding AddressFieldset
$this->add(array(
'name' => 'address',
'type' => \User\Form\AddressFieldset::class,
));
/* ... */
$this->add(array(
'type' => 'datetime',
'name' => 'created',
'options' => array(
'label' => $this->translator->translate('label.created', 'Form'),
'format' => 'd.m.Y H:i',
'label_attributes' => array(
'class' => 'col-sm-3',
),
'column-size' => 'sm-5',
),
'attributes' => array(
'id' => 'created',
),
));
}
public function getInputFilterSpecification()
{
return array(
'firstName' => array(
'required' => true,
'filters' => array(
array('name' => 'StringTrim'),
array('name' => 'StripTags'),
),
'validators' => array(),
),
/* ... */
'password' => array(
'required' => true,
'filters' => array(
array('name' => 'StringTrim'),
array('name' => 'StripTags'),
[
'name' => PasswordFilter::class,
'options' => [
'object' => $this->getObject(),
'field' => 'password'
]
]
),
'validators' => array(),
),
'passwordVerify' => array(
'required' => true,
'filters' => [
[
'name' => PasswordFilter::class,
'options' => [
'object' => $this->getObject(),
'field' => 'password'
]
]
],
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'min' => 6
)
),
array(
'name' => 'Identical',
'options' => array(
'token' => 'password'
)
)
)
),
/* ... */
'created' => array(
'required' => false,
'filters' => array(
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'Date',
'options' => array('format' => 'd.m.Y H:i')
),
),
)
);
}
}
PasswordFilter:
class PasswordFilter extends AbstractFilter
{
/** #var EntityManager */
protected $entityManager;
/** #var PasswordInterface */
protected $passwordManager;
/**
* PasswordFilter constructor.
* #param EntityManager $entityManager
* #param PasswordInterface $passwordManager
* #param array $options
*/
public function __construct(EntityManager $entityManager, PasswordInterface $passwordManager, $options = [])
{
$this->entityManager = $entityManager;
$this->passwordManager = $passwordManager;
$this->options = $options;
}
public function filter($value)
{
$object = $this->getOption('object');
$field = $this->getOption('field');
$getter = 'get'.ucfirst($field);
if (!$object || !$field) {
throw new \Exception('Options "object" and "field" are required.');
}
if ($object->getId()) {
$dbObject = $this->entityManager->getRepository(get_class($object))->find($object->getId());
if ($value === $dbObject->{$getter}()) {
return $value;
}
}
// hash password here...
return $this->passwordManager->create($value);
}
private function getOption($option)
{
if (array_key_exists($option, $this->options)) {
return $this->options[$option];
}
return false;
}
}
Any clues? Do I call remove inputfilter of "passwordVerify" to early in the process of instantiation?
I also tested to remove the inputFilter and field after "$this->form->bind($user)" in my controller, which also works. Why it does not work then if I remove it in Admin\UserForm, which is in my opinion the cleaner way of managing the "passwordVerify"-stuff?
If you call $this->getInputFilter(), the InputProviderInterface::getInputSpecification method in your UserForm is being called.
If you did not attached the object already, it cannot retrieve it.
But I dont get why you would even need that. You are hashing the password if the value does not fit to the database value, but obviously the database value seems to be plain text as the input or why would you compare it?
IMHO you just should hash the password, no matter what the current value in your database is.
If you are using doctrine and the password wont change, it wont execute an UPDATE query anyway.

ZF2 + Doctrine2 - Fieldset in Fieldset of a Collection in Fieldset does not validate properly

I asked a similar question a while ago, which came down to the structuring of the Forms, Fieldsets and InputFilters.
I've been thoroughly applying the principle of separation of concerns to split up Fieldsets from InputFilters as the modules they're created in will also be used in an API (Apigility based), so I would need only Entities and InputFilters.
However, I now have a problem that when I have a Fieldset, used by a Fieldset, used in a Collection in a Fieldset, that the inner-most Fieldset does not validate.
Let me elaborate with examples, and code!
The situation is that I want to be able to create a Location. A Location consists of a property name and a OneToMany ArrayCollection|Address[] association. This is because a Location could have multiple addresses (such as a visitors address and a delivery address).
An Address consists of a few properties (street, number, city, Country, etc.) and a OneToOne Coordinates association.
Now, Address has the below Fieldset:
class AddressFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
// More properties, but you get the idea
$this->add([
'name' => 'street',
'required' => false,
'type' => Text::class,
'options' => [
'label' => _('Street'),
],
]);
$this->add([
'name' => 'country',
'required' => false,
'type' => ObjectSelect::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Country::class,
'property' => 'id',
'is_method' => true,
'find_method' => [
'name' => 'getEnabledCountries',
],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Country'),
'label_generator' => function ($targetEntity) {
return $targetEntity->getName();
},
],
]);
$this->add([
'type' => CoordinatesFieldset::class,
'required' => false,
'name' => 'coordinates',
'options' => [
'use_as_base_fieldset' => false,
],
]);
}
}
As you can see, details for the Address Entity must entered, a Country must be selected and Coordinates could (not required) be provided.
The above is validated using the InputFilter below.
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** #var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
protected $coordinatesFieldsetInputFilter;
public function __construct(
CoordinatesFieldsetInputFilter $filter,
EntityManager $objectManager,
Translator $translator
) {
$this->coordinatesFieldsetInputFilter = $filter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Address::class),
'translator' => $translator,
]);
}
/**
* Sets AddressFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'street',
'required' => false,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
$this->add([
'name' => 'country',
'required' => false,
]);
}
}
As you can see, the AddressFieldsetInputFilter required a few things, one of which is the CoordinatesFieldsetInputFilter. In the init() function this is then added with the name corresponding to that given in the Fieldset.
Now, all of the above works, no problem. Addresses with Coordinates everywhere. It's great.
The problem arises when we go another level further and have the LocationFieldset, as below, with it's LocationFieldsetInputFilter.
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
$this->add([
'type' => Collection::class,
'name' => 'addresses',
'options' => [
'label' => _('Addresses'),
'count' => 1,
'allow_add' => true,
'allow_remove' => true,
'should_create_template' => true,
'target_element' => $this->getFormFactory()->getFormElementManager()->get(AddressFieldset::class),
],
]);
}
}
In the class below, you might notice a bunch of commented out lines, these have been different attempts to modify the DI and/or setup of the InputFilter so that it works.
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
protected $addressFieldsetInputFilter;
// /** #var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
// protected $coordinatesFieldsetInputFilter;
public function __construct(
AddressFieldsetInputFilter $filter,
// CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter,
EntityManager $objectManager,
Translator $translator
) {
$this->addressFieldsetInputFilter = $filter;
// $this->coordinatesFieldsetInputFilter = $coordinatesFieldsetInputFilter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'addresses');
// $this->get('addresses')->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
You might have noticed that the LocationFieldset and LocationFieldsetInputFilter make use of the existing AddressFieldset and `AddressFieldsetInputFilter.
Seeing as how they work, I cannot figure out why it's going wrong.
But what goes wrong?
Well, to create a Location, it appears that entering Coordinates is always required. If you look in the AddressFieldset (at the top), you'll notice a 'required' => false,, so this makes no sense.
However, when I DO enter values in the inputs, they do not get validated. When debugging, I get into the \Zend\InputFilter\BaseInputFilter, line #262 where it specifically validates the input, and I notice that it has lost it's data along the way of validation.
I've confirmed the presence of the data at the start, and during the validation, up until it tries to validate the Coordinates Entity, where it seems to lose it (haven't found out why).
If someone could point me in the right direction to clean this up, that help would be greatly appreciated. Have been banging against this issue for way too many hours now.
EDIT
Added in view partial code to show method for printing, in case that should/could help:
address-form.phtml
<?php
/** #var \Address\Form\AddressForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('address')->get('id'));
echo $this->formRow($form->get('address')->get('street'));
echo $this->formRow($form->get('address')->get('city'));
echo $this->formRow($form->get('address')->get('country'));
echo $this->formCollection($form->get('address')->get('coordinates'));
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
location-form.phtml
<?php
/** #var \Location\Form\LocationForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('location')->get('id'));
echo $this->formRow($form->get('location')->get('name'));
//echo $this->formCollection($form->get('location')->get('addresses'));
$addresses = $form->get('location')->get('addresses');
foreach ($addresses as $address) {
echo $this->formCollection($address);
}
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
And just in case it makes it all even more clear: a debug picture to help out
After another day of debugging (and swearing), I found the answer!
This SO question helped me out by pointing me towards the Zend CollectionInputFilter.
Because the AddressFieldset is added to the LocationFieldset within a Collection, it must be validated using a CollectionInputFilter which has the specific InputFilter for the Fieldset specified.
To fix my application I had to modify both the LocationFieldsetInputFilter and the LocationFieldsetInputFilterFactory. Below the updated code, with the old code in comments.
LocationFieldsetInputFilterFactory.php
class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
/**
* #param ServiceLocatorInterface|ControllerManager $serviceLocator
* #return InputFilter
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
parent::setupRequirements($serviceLocator, Location::class);
/** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager')
->get(AddressFieldsetInputFilter::class);
$collectionInputFilter = new CollectionInputFilter();
$collectionInputFilter->setInputFilter($addressFieldsetInputFilter); // Make sure to add the FieldsetInputFilter that is to be used for the Entities!
return new LocationFieldsetInputFilter(
$collectionInputFilter, // New
// $addressFieldsetInputFilter, // Removed
$this->getEntityManager(),
$this->getTranslator()
);
}
}
LocationFieldsetInputFilter.php
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
// Removed
// /** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
// protected $addressFieldsetInputFilter ;
// New
/** #var CollectionInputFilter $addressCollectionInputFilter */
protected $addressCollectionInputFilter;
public function __construct(
CollectionInputFilter $addressCollectionInputFilter, // New
// AddressFieldsetInputFilter $filter, // Removed
EntityManager $objectManager,
Translator $translator
) {
// $this->addressFieldsetInputFilter = $filter; // Removed
$this->addressCollectionInputFilter = $addressCollectionInputFilter; // New
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
// $this->add($this->addressFieldsetInputFilter, 'addresses'); // Removed
$this->add($this->addressCollectionInputFilter, 'addresses'); // New
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
The way this works is that, during the validation of the data, it will apply the singular AddressFieldsetInputFilter to every "element" received from the client-side. Because a Collection, from the client, may be 0 or more of these elements (as adding/removing them is done using JavaScript).
Now that I've figured that out, it does actually make perfect sense.

uploading a picture using forms in symfony

I need to upload images using symfony, but ive not been able to do it using my form...
A simplified model is:
Offer:
columns:
id:
name:
pic:
flyer:
description:
.
.
.
relations:
Picture:
local: pic
foreign: id
type: one
Picture_2:
class: Picture
local: flyer
foreign: id
type: one
Picture:
columns:
id:
path:
name:
.
.
.
Now, im using a form that extends OfferForm, since I need my form to have file widgets instead of a choice widget for the fields 'pic' and 'flyer'. In the saving process, then, i need to create two instances of 'Picture' to create the two Picture objects associated to this Offer.
I have not managed to find good and complete documentation on uploading files... or at least not to my case... every tutorial or article magically uses the $form->save() method, and all goes fine!, but i have had multiple errors while doing this...
this is my form class:
class myOfferForm extends OfferForm {
protected $statusChoices = array(
'A' => 'Active',
'E' => 'Expired',
);
protected $validStatus = array('A','E');
public function configure() {
parent::configure();
$this->setWidgets(array(
'id' => new sfWidgetFormInputHidden(),
'name' => new sfWidgetFormInputText(),
'picFile' => new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getPicFileSrc(),
'is_image' => true,
'with_delete' => !is_null($this->getObject()->getPicFileSrc())
)),
'flyerFile' => new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getFlyerFileSrc(),
'is_image' => true,
'with_delete' => !is_null($this->getObject()->getFlyerFileSrc())
)),
'from' => new sfWidgetFormDate(array(
'format' => '%day%/%month%/%year%',
'can_be_empty' => false,
'default' => date('Y/m/d')
)),
'to' => new sfWidgetFormDate(array(
'format' => '%day%/%month%/%year%',
'can_be_empty' => false,
'default' => date('Y/m/d')
)),
'description' => new sfWidgetFormTextarea(),
'status' => new sfWidgetFormChoice(array(
'choices' => $this->statusChoices)),
'products' => new sfWidgetFormDoctrineChoice(array(
'model' => 'Product',
'table_method' => 'getActivesOrderedByName',
'add_empty' => 'Check associated products',
'multiple' => true,
)
),
));
$this->widgetSchema->setLabels(array(
'id' => '',
'name' => 'Name *:',
'picFile' => 'Picture *:',
'flyerFile' => 'Flyer *:',
'from' => 'Valid From *:',
'to' => 'Valid To *:',
'description' => 'Description :',
'status' => 'Status *:',
'products' => 'Associated Products :',
));
$this->setValidators(array(
'id' => new sfValidatorChoice(array(
'choices' => array($this->getObject()->get('id')),
'empty_value' => $this->getObject()->get('id'),
'required' => false,
)),
'name' => new sfValidatorString(),
'picFile' => new sfValidatorFile(array(
'required' => false,
'mime_types' => 'web_images',
'path' => WebPromocion::getStaticDirPath().'/',
'validated_file_class' => 'OfferValidatedFile',
)),
'flyerFile' => new sfValidatorFile(array(
'required' => false,
'mime_types' => 'web_images',
'path' => WebPromocion::getStaticDirPath().'/',
'validated_file_class' => 'OfferValidatedFile',
)),
'from' => new sfValidatorDate(),
'to' => new sfValidatorDate(),
'description' => new sfValidatorString(),
'status' => new sfValidatorChoice(array(
'choices' => $this->validStatus,
'required' => true,
)),
'products' => new sfValidatorDoctrineChoice(array(
'required' => false,
'model' => 'Product',
'column' => 'id',
'multiple' => true, )),
));
$this->validatorSchema['fotoFile_delete'] = new sfValidatorPass();
$this->validatorSchema['flyerFile_delete'] = new sfValidatorPass();
$this->widgetSchema->setIdFormat('offer_form_%s');
$this->widgetSchema->setNameFormat('offer[%s]');
}
}
The OfferValidatedFile class:
class OfferValidatedFile extends sfValidatedFile {
/**
* Generates a random filename for the current file, in case
* it already exists.
*
* #return string A convenient name to represent the current file
*/
public function generateFilename()
{
$filename = $this->getOriginalName().$this->getExtension($this->getOriginalExtension());
if (file_exits(WebPromocion::getStaticDirSrc().$filename)) {
return sha1($this->getOriginalName().rand(11111, 99999)).$this->getExtension($this->getOriginalExtension());
} else {
return $filename;
}
}
}
And, in my action, I am doing, along with other stuff:
$this->form->save()
There is a problem. The Offer object does not have any existing Picture object to be associated with by the time the form is saved....
I think the main problem is that i want to use one form to handle submition of information related to two different objects.
So, what am I doing wrong? what am I not doing? Does someone knows a complete documentation on this subject that i could use? Is there a clean symfonian way to do this?
This documentation is for the version of symfony and doctrine you're using. Unfortunately, rather than outlining initial setup for you, they include an installer script. Note that this type of setup is outlined elsewhere in both 'A Gentle Introduction to Symfony' as well as in other parts of 'More with Symfony' for version 1.4. Fortunately there is also a fairly verbose look at refactoring the script's form class in the first link which I think would benefit you quite a bit as well - It explains the model's processing of the form (Where you appear to be having issues) quite well, which may make debugging a little less mystifying for you.
I recommend giving it a good read. I followed this for a project a few months ago and came out of it without any issues.

Adding Custom attribute in forms

I am using magento V1.5. I am working on Customer EAV & have tried to create another EAV module.
I have added few attributes in customer entity. Now MY requirement is those attributes must be editable from frontend as well as backend.
Please tell me how to add those attributes in forntend form (customer edit form). & Tell me what to do in backend to have those options to be editable.
I am thinking if there is a way in admin like in our Form.php we prepare form with adding elements to it. then we not need to write a code to create actual html. Magento automatically does it. SO idea is it must also load the new attributes in that was just added. (like they appear in product edit.)
Second Issue is, Can u guys tell me what should I write in my Grid.php >> prepareCollection (for other EAV module ). so that it must get all the attributes with their values ( or may be few )
here is something that I have in my Grid.php but its not working
protected function _prepareCollection()
{
$collection = Mage::getModel('pincodes/eavpincodes')->getCollection();
$this->setCollection($collection);
return parent::_prepareCollection();
}
& this is my collection file
class Namespace_Pincodes_Model_Resource_Eav_Mysql4_Eavpincodes_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
{
protected function _construct()
{
$this->_init('pincodes/eavpincodes');
}
}
But its not returning anything in my grid
& here is my Attribute collection file
class Inkfruit_Pincodes_Model_Resource_Eav_Mysql4_Attribute_Collection extends Mage_Eav_Model_Mysql4_Entity_Attribute_Collection
{
public function _construct()
{
$this->_init('pincodes/resource_eav_attribute', 'eav/entity_attribute');
}
protected function _initSelect()
{
$this->getSelect()->from(array('main_table' => $this->getResource()->getMainTable()))
->where('main_table.entity_type_id=?', Mage::getModel('eav/entity')->setType('pincodes_eavpincodes')->getTypeId())
->join(
array('additional_table' => $this->getTable('pincodes/eavpincodes')),
'additional_table.attribute_set_id=main_table.attribute_id' // I think this sql need to be changed but I have no idea what it'll be
);
return $this;
}
}
Guys thank you so much. This forum has been specially very helpful to me
Regards
SAM
Ok guys I have created a separate module for customers that has one mysql4_install file & it goes as follows
$installer = $this;
$installer->startSetup();
$setup = new Mage_Eav_Model_Entity_Setup('core_setup');
$setup->addAttribute('customer', 'profile_image', array(
'label' => 'Profile Image',
'type' => 'varchar',
'input' => 'file',
'visible' => true,
'required' => false,
'user_defined' => true,
));
$setup->addAttribute('customer', 'mobile', array(
'label' => 'Mobile Number',
'type' => 'int',
'input' => 'text',
'visible' => true,
'required' => false,
'user_defined' => true,
));
$installer->endSetup();
$installer->installEntities();
But when I hit the url for this module I see no error but these attributes are not in my database.
I also have created Entity_Setup file & it goes as follows I think its incorrect but I gave it a try
<?php
class Namespace_Customer_Entity_Setup extends Mage_Eav_Model_Entity_Setup
{
public function getDefaultEntities()
{
return array (
'customer' => array(
'entity_model' => 'customer/customer',
'attribute_model' => 'customer/attribute',
'table' => 'customer/entity',
'attributes' => array(
'profile_image' => array(
//the EAV attribute type, NOT a mysql varchar
'type' => 'varchar',
'backend' => '',
'frontend' => '',
'label' => 'Profile Image',
'input' => 'file',
'class' => '',
'source' => '',
// store scope == 0
// global scope == 1
// website scope == 2
'global' => 0,
'visible' => true,
'required' => false,
'user_defined' => true,
'default' => '',
'searchable' => true,
'filterable' => true,
'comparable' => false,
'visible_on_front' => false,
'unique' => false
),
'mobile' => array(
'type' => 'varchar',
'backend' => '',
'frontend' => '',
'label' => 'Mobile Number',
'input' => 'text',
'class' => '',
'source' => '',
'global' => 0,
'visible' => true,
'required' => true,
'user_defined' => true,
'default' => '',
'searchable' => true,
'filterable' => true,
'comparable' => false,
'visible_on_front' => false,
'unique' => false
),
),
)
);
}
}
But I see nothing, not any new attribute in database.
Can u guys help here whats wrong??
Thanks
Guys here is My solution. the above answers worked for adding attribute to database only
My question has three parts.
1. Adding attributes to entity in database.
Ans. the above install scripts worked. using a Setup.php & mysql_install/ mysql_upgrade script.
2. Attribute should be editable from Frontend
Ans. We need to modify the file app/design/fontend/default//template/customer/form/edit.phtml
3. Attribute must be editable from Backend
Ans. for that we need to add this snippet in our config.xml
<global>
<fieldsets>
<customer_account>
<create>1</create><update>1</update>
</customer_account>
</fieldsets>
</global>
this will do everything
Hope this will help somebody
This worked for me, notice the additional lines:
<?php
class Millena_CustomerExportAdditions_Model_Resource_Eav_Mysql4_Setup extends Mage_Eav_Model_Entity_Setup
{
public function getDefaultEntities()
{
return array(
'customer' => array(
'entity_model' =>'customer/customer',
'attribute_model' => 'customer/attribute',
'table' => 'customer/entity',
'additional_attribute_table' => 'customer/eav_attribute',
'entity_attribute_collection' => 'customer/attribute_collection',
'attributes' => array(
'export_status' => array(
//'group' => 'Group/Tab',
'label' => 'Customer Export Status',
'type' => 'int',
'input' => 'select',
'default' => '0',
'class' => '',
'backend' => '',
'frontend' => '',
'source' => 'millena_customerExportAdditions/customer_attribute_source_exportStatus',
'global' => 2, //global scope
'visible' => true,
'required' => false,
'user_defined' => false,
'searchable' => false,
'filterable' => false,
'comparable' => false,
'visible_on_front' => false,
'visible_in_advanced_search' => false,
'unique' => false
)
)
)
);
}
}
Install script is simply:
<?php
$installer = $this;
$installer->installEntities();
Having same issue here. Using addAttribute in 1.5.0.1 seems to add it into database but customer admin won't render it.
This method works fine with product and category attributes. And in 1.4.0.0 it works for customers too.
In Magento chagelog there is bullet point under improvements about customer attribute rendering. I'll start checking it next.

Categories