logo
Ask your Symfony questions! Pay money and get answers fast! (more info)

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.

$20
EAV Entity Attribute Value sfForm

Hi,

I have a need to have some forms user-editable in an application. There are different types of customers, and each customer gets assigned an attribute set, composed of multiple attributes. The closest example I could provide is the way magento handles product attributes. I'm using symfony 1.4 and doctrine.

For my purposes, I'd need to have various types of inputs (radio, text, dropdown select), and for select fields I'd need to have values in the database.

So, here's a basic schema I envisioned:

AttributeSet:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(255), notnull: true }
relations:
CustomerAttribute: { refClass: AttributeSetCustomerAttribute, local: attribute_set_id, foreign: customer_attribute_id }

AttributeSetCustomerAttribute:
options:
symfony:
form: false
filter: false
columns:
attribute_set_id: { type: integer(4), primary: true }
customer_attribute_id: { type: integer(4), primary: true }

Customer:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(255), notnull: true }
relations:
AttributeSet: { local: attribute_set_id, foreign: id }

CustomerAttribute:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(100), notnull: true }
label: { type: string(100) }
input_type: { type: enum, values: [Dropdown, TextField, TextArea, Date, Price, MultiSelect] }
visible: { type: boolean }
required: { type: boolean }
searchable: { type: boolean }
comparable: { type: boolean }
layered_use: { type: boolean }
relations:
AttributeSet: { refClass: AttributeSetProductAttribute, local: customer_attribute_id, foreign: attribute_set_id }

CustomerAttributeOption:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
attribute_id: { type: integer(4) }
value: { type: string(100) }
relations:
CustomerAttribute: { local: id, foreign: id }

CustomerAttributeValue:
actAs: [ Softdelete ]
columns:
customer_id: { type: integer(4), primary: true }
attribute_id: { type: integer(4), primary: true }
value: { type: string(150) }


How would I make this happen using the sfForms in symfony? I would like a specific example of making a form that creates a customer form from an attribute set, and builds those attributes with at least one dropdown select built from options in the database.

This question has been answered.

webguy | 05/13/10 at 9:11am Edit


(3) Responses

See a threaded view of answers?

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

  • avatar
    Last edited:
    05/13/10
    12:44pm
    Jarret Minkler says:

    So, basically what you want to use is the widget that has 2 select boxes with an arrow between them to select multiple attributes that a customer can have. I believe if your schema is setup correctly the admin generator may do this for you.

    Also you may want to look at embedding the form

    $this->embedRelation('AttributeSet');

    Which will embed multiple AtributeSet forms on the creation of the Customer, you can then setup the single AttributeSet form or extend it to suite your needs.

    Previous versions of this answer: 05/13/10 at 12:44pm

  • avatar
    Last edited:
    05/18/10
    3:42pm
    Jakub Zalas says:

    I'm not sure if your schema is perfect. However, I tried to make it working and needed to change it a bit.

    I wrote one action which uses the eav form. It looks up customer and passes it to the form. Attribute values are loaded. Once you save values are saved in the database.

    This is a proof of concept and I strongly advice you to refactor it before putting it on production. Some methods need to be moved to model, other need to be merged. Generally cleanups are needed :)

    You can download the source of project I used: eav.tgz


    AttributeSet:
    columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    name: { type: string(255), notnull: true }
    relations:
    CustomerAttribute: { refClass: AttributeSetCustomerAttribute, local: attribute_set_id, foreign: customer_attribute_id }

    AttributeSetCustomerAttribute:
    options:
    symfony:
    form: false
    filter: false
    columns:
    attribute_set_id: { type: integer(4), primary: true }
    customer_attribute_id: { type: integer(4), primary: true }

    Customer:
    columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    name: { type: string(255), notnull: true }
    attribute_set_id: { type: integer(4), primary: false }
    relations:
    AttributeSet: { local: attribute_set_id, foreign: id }

    CustomerAttribute:
    columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    name: { type: string(100), notnull: true }
    label: { type: string(100) }
    input_type: { type: enum, values: [Dropdown, TextField, TextArea, Date, Price, MultiSelect] }
    visible: { type: boolean }
    required: { type: boolean }
    searchable: { type: boolean }
    comparable: { type: boolean }
    layered_use: { type: boolean }
    relations:
    AttributeSet: { refClass: AttributeSetCustomerAttribute, local: customer_attribute_id, foreign: attribute_set_id }

    CustomerAttributeOption:
    columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    attribute_id: { type: integer(4) }
    value: { type: string(100) }
    relations:
    CustomerAttribute: { local: attribute_id, foreign: id }

    CustomerAttributeValue:
    actAs: [ SoftDelete ]
    columns:
    customer_id: { type: integer(4), primary: true }
    attribute_id: { type: integer(4), primary: true }
    value: { type: string(150) }
    relations:
    Customer: { local: customer_id, foreign: id, type: one }
    CustomerAttribute: { local: attribute_id, foreign: id, type: one }


    Here is action:


    class eavActions extends sfActions
    {
    public function executeIndex(sfWebRequest $request)
    {
    $customer = Doctrine_Core::getTable('Customer')->findOneByName('Zend');
    $this->forward404Unless($customer);

    $this->form = new CustomerEavForm(array(), array('customer' => $customer));

    if ($request->isMethod('post'))
    {
    $this->form->bind($request->getParameter($this->form->getName()));

    if ($this->form->isValid())
    {
    $this->form->save();
    }
    }
    }
    }


    The form:


    class CustomerEavForm extends sfForm
    {
    public function __construct($defaults = array(), $options = array(), $CSRFSecret = null)
    {
    parent::__construct($defaults, $options, $CSRFSecret);

    if (!isset($options['customer']))
    {
    throw new Exception('customer option is required');
    }

    $this->createWidgetsAndValidators();

    $this->loadDefaults();

    $this->getWidgetSchema()->setNameFormat('customer[%s]');
    }

    private function loadDefaults()
    {
    $customer = $this->getOption('customer');
    $customerAttributes = $customer->getAttributeSet()->getCustomerAttribute();

    foreach ($customerAttributes as $customerAttribute)
    {
    $customerAttributeValues = $customerAttribute->getCustomerAttributeValue();
    foreach ($customerAttributeValues as $customerAttributeValue)
    {
    if ($customerAttributeValue->getCustomerId() == $customer->getId())
    {
    $this->setDefault($customerAttribute->getName(), $customerAttributeValue->getValue());
    }
    }
    }
    }

    private function createWidgetsAndValidators()
    {
    $customer = $this->getOption('customer');
    $customerAttributes = $customer->getAttributeSet()->getCustomerAttribute();

    foreach ($customerAttributes as $customerAttribute)
    {
    $this->createWidgetAndValidatorForAttributeSet($customerAttribute);
    }
    }

    private function createWidgetAndValidatorForAttributeSet(CustomerAttribute $customerAttribute)
    {
    $inputType = $customerAttribute->getInputType();
    $method = sprintf('createWidgetAndValidatorFor%sInputType', $inputType);
    $this->$method($customerAttribute);
    }

    private function createWidgetAndValidatorForDropdownInputType(CustomerAttribute $customerAttribute, $multiple = false)
    {
    $query = Doctrine_Core::getTable('CustomerAttributeOption')
    ->createQuery('cao')
    ->addWhere('cao.attribute_id = ?', $customerAttribute->getId());

    $this->setWidget(
    $customerAttribute->getName(),
    new sfWidgetFormDoctrineChoice(array(
    'query' => $query,
    'model' => 'CustomerAttributeOption',
    'method' => 'getValue',
    'multiple' => $multiple
    ))
    );
    $this->setValidator(
    $customerAttribute->getName(),
    new sfValidatorDoctrineChoice(array(
    'query' => $query,
    'model' => 'CustomerAttributeOption',
    'multiple' => $multiple,
    'required' => $customerAttribute->getRequired()
    ))
    );
    }

    private function createWidgetAndValidatorForTextFieldInputType(CustomerAttribute $customerAttribute)
    {
    $this->setWidget($customerAttribute->getName(), new sfWidgetFormInput());
    $this->setValidator($customerAttribute->getName(), new sfValidatorString(array('required' => $customerAttribute->getRequired())));
    }

    private function createWidgetAndValidatorForTextAreaInputType(CustomerAttribute $customerAttribute)
    {
    $this->setWidget($customerAttribute->getName(), new sfWidgetFormTextarea());
    $this->setValidator($customerAttribute->getName(), new sfValidatorString(array('required' => $customerAttribute->getRequired())));
    }

    private function createWidgetAndValidatorForDateInputType(CustomerAttribute $customerAttribute)
    {
    $this->setWidget($customerAttribute->getName(), new sfWidgetFormDate());
    $this->setValidator($customerAttribute->getName(), new sfValidatorDate(array('required' => $customerAttribute->getRequired())));
    }

    private function createWidgetAndValidatorForPriceInputType(CustomerAttribute $customerAttribute)
    {
    $this->setWidget($customerAttribute->getName(), new sfWidgetFormInput());
    $this->setValidator($customerAttribute->getName(), new sfValidatorInteger(array('required' => $customerAttribute->getRequired())));
    }

    private function createWidgetAndValidatorForMultiSelectInputType(CustomerAttribute $customerAttribute)
    {
    $this->createWidgetAndValidatorForDropdownInputType($customerAttribute, true);
    }
    public function save()
    {
    $customer = $this->getOption('customer');
    $customerAttributes = $customer->getAttributeSet()->getCustomerAttribute();

    foreach ($customerAttributes as $customerAttribute)
    {
    $customerAttributeValues = $customerAttribute->getCustomerAttributeValue();
    foreach ($customerAttributeValues as $customerAttributeValue)
    {
    if ($customerAttributeValue->getCustomerId() == $customer->getId())
    {
    $customerAttributeValue->setValue($this->getValue($customerAttribute->getName()));
    $customerAttributeValue->save();
    }
    }
    }
    }
    }


    Fixtures:


    AttributeSet:
    set_1:
    name: Attribute set 1
    set_2:
    name: Attribute set 2

    Customer:
    customer_zend:
    name: Zend
    AttributeSet: set_1
    customer_ibm:
    name: IBM
    AttributeSet: set_2

    AttributeSetCustomerAttribute:
    asca_01:
    AttributeSet: set_1
    CustomerAttribute: ca_title
    asca_02:
    AttributeSet: set_1
    CustomerAttribute: ca_type
    asca_03:
    AttributeSet: set_1
    CustomerAttribute: ca_description
    asca_04:
    AttributeSet: set_1
    CustomerAttribute: ca_created_at
    asca_05:
    AttributeSet: set_1
    CustomerAttribute: ca_price

    CustomerAttribute:
    ca_title:
    name: title
    label: Title
    input_type: TextField
    required: true
    ca_type:
    name: type
    label: Type
    input_type: Dropdown
    required: true
    ca_description:
    name: description
    label: Description
    input_type: TextArea
    required: true
    ca_created_at:
    name: created_at
    label: Created at
    input_type: Date
    required: true
    ca_price:
    name: price
    label: Price
    input_type: Price
    required: true

    CustomerAttributeOption:
    cao_type_01:
    CustomerAttribute: ca_type
    value: Simple
    cao_type_02:
    CustomerAttribute: ca_type
    value: Extended
    cao_type_03:
    CustomerAttribute: ca_type
    value: Full

    CustomerAttributeValue:
    cav_01:
    Customer: customer_zend
    CustomerAttribute: ca_title
    value: Title
    cav_02:
    Customer: customer_zend
    CustomerAttribute: ca_type
    value: Extended
    cav_03:
    Customer: customer_zend
    CustomerAttribute: ca_description
    value: Lorem lipsum
    cav_04:
    Customer: customer_zend
    CustomerAttribute: ca_created_at
    value: 2010-05-13
    cav_05:
    Customer: customer_zend
    CustomerAttribute: ca_price
    value: 200

    Previous versions of this answer: 05/13/10 at 4:12pm

  • avatar
    Last edited:
    05/13/10
    12:51pm
    webguy says:

    I will not be using this within the admin generator.

    The attribute sets and attribute options will be managed in an independent form.

    When creating a customer, the user would select an attribute set and then the attributes would be output.

This question has expired.





Current status of this question: Completed



Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.