Magento – Programmatically remove attribute from attribute-set

attributes

I've tried removing an eav_entity_attribute using this code:

$entAttr = Mage::getModel('eav/entity_attribute')->getCollection()
        ->addFieldToFilter('attribute_set_id',$set->getId())
        ->addFieldToFilter('attribute_id',$attId)->getFirstItem();


    $entAttr->delete();

but it doesn't work. I receive this error:
Column not found: 1054 Unknown column 'attribute_set_id' in 'where clause''

How can I delete an eav_entity_attribute this way?

Best Answer

The short version

Mage::getModel('catalog/product_attribute_set_api')->attributeRemove($attId, $set->getId());

Your error message isn't popping up due to your delete method call, it's popping up due to your collection use. For reasons lost to the mystery and smog of Los Angeles, the eav/entity_attribute resource model class is initialized with the eav/attribute resource string.

#File: app/code/core/Mage/Eav/Model/Resource/Entity/Attribute.php
protected function _construct()
{
    $this->_init('eav/attribute', 'attribute_id');
}

This means the following collection

$collection = Mage::getModel('eav/entity_attribute')->getCollection();

Actually queries the eav_attribute table.

$sql = $collection->getSelect()->__toString();
echo($sql);

//prints SELECT `main_table`.* FROM `eav_attribute` AS `main_table`

@DavidTay was actually on the right track. Whenever you're in doubt about how to do something in Magento, look at how the core team themselves did it. However, while looking at the admin console code for this will lead you to a method for removing your attribute from a attribute set, it's even better to look at API implementation code. This API code has an implicit promise of doing things in a stable way, where a lot of the early admin console code shows the scars of having been developed rapidly.

If you take a look at the removeAttribute implementation for the attribute set api class, you'll find your answer.

#File: app/code/core/Mage/Catalog/Model/Product/Attribute/Set/Api.php
public function attributeRemove($attributeId, $attributeSetId)
{
    // check if attribute with requested id exists
    /** @var $attribute Mage_Eav_Model_Entity_Attribute */
    $attribute = Mage::getModel('eav/entity_attribute')->load($attributeId);
    if (!$attribute->getId()) {
        $this->_fault('invalid_attribute_id');
    }
    // check if attribute set with requested id exists
    /** @var $attributeSet Mage_Eav_Model_Entity_Attribute_Set */
    $attributeSet = Mage::getModel('eav/entity_attribute_set')->load($attributeSetId);
    if (!$attributeSet->getId()) {
        $this->_fault('invalid_attribute_set_id');
    }
    // check if attribute is in set
    $attribute->setAttributeSetId($attributeSet->getId())->loadEntityAttributeIdBySet();
    if (!$attribute->getEntityAttributeId()) {
        $this->_fault('attribute_is_not_in_set');
    }
    try {
        // delete record from eav_entity_attribute
        // using entity_attribute_id loaded by loadEntityAttributeIdBySet()
        $attribute->deleteEntity();
    } catch (Exception $e) {
        $this->_fault('remove_attribute_error', $e->getMessage());
    }

    return true;
}

Parsing this code out from it's API error checking — first you load an eav/entity_attribute model by it's attribute id.

$attribute = Mage::getModel('eav/entity_attribute')->load($attributeId); 

Remember, for reasons we don't know, this actually loads data from the eav_attribute table because of what's in the resource model _construct.

Next, we set the attribute set id on the eav/entity_attribute model.

$attribute->setAttributeSetId($attributeSet->getId())->loadEntityAttributeIdBySet();

Then, we call the object's deleteEntity method, which actually removes the data from the correct table (eav_entity_attribute)

// delete record from eav_entity_attribute
// using entity_attribute_id loaded by loadEntityAttributeIdBySet()
$attribute->deleteEntity();

If you trace the deleteEntity method to the model

#File: app/code/core/Mage/Eav/Model/Entity/Attribute.php
public function deleteEntity()
{
    return $this->_getResource()->deleteEntity($this);
}

and then to the resource model

#File: app/code/core/Mage/Eav/Model/Resource/Entity/Attribute.php
public function deleteEntity(Mage_Core_Model_Abstract $object)
{
    if (!$object->getEntityAttributeId()) {
        return $this;
    }

    $this->_getWriteAdapter()->delete($this->getTable('eav/entity_attribute'), array(
        'entity_attribute_id = ?' => $object->getEntityAttributeId()
    ));

    return $this;
}

you can see that ultimately, Magento is using a DELETE query with the write adapter to remove the row.

Rather than do this yourself every-time, you can just call the API method directly. Not via XML-RPC or SOAP, but by manually instantiating the API implementation class

Mage::getModel('catalog/product_attribute_set_api')->attributeRemove($attributeId, $attributeSetId);