This is the second part of our discussion on Magento Bundle product type. In the first part we concentrated on the back-end management of bundle products and on the front-end display specifics. In this post we will discuss the functionality implemented by the Bundle product type class; but first, let’s look at bundle options and selection classes.
Bundle Option and Selection Classes
Bundle options are represented by model class Mage_Bundle_Model_Option and a resource model Mage_Bundle_Model_Resource_Option. The option data is saved in two tables: catalog_product_bundle_option and catalog_product_bundle_option_value. The first table contains the general option data such as:
- option_id – a unique option identifier;
- parent_id – ID of the bundle product to which the option belongs to;
- required – a flag indicating that a bundle can’t be added to cart without this option being configured;
- position – parameter used to sort lists of bundle options in an ascending order;
- type – one of four allowed option types: drop-down, radio buttons, multiselect, and checkbox. The string constants corresponding to each type are defined in the Mage_Catalog_Model_Product_Option class:
class Mage_Catalog_Model_Product_Option extends Mage_Core_Model_Abstract { /**code omitted for brevity **/ const OPTION_TYPE_DROP_DOWN = 'drop_down'; const OPTION_TYPE_RADIO = 'radio'; const OPTION_TYPE_CHECKBOX = 'checkbox'; const OPTION_TYPE_MULTIPLE = 'multiple'; /**code omitted for brevity **/ }
Listing 5. Option type constants, /app/code/core/Mage/Catalog/Model/Product/Option.php, line 65
The second table, catalog_product_bundle_option_value, stores option titles. They need a table of their own because titles can be different depending on the store view. If you have multiple language-specific store-views this feature is very helpful.
Selections are implemented in class Mage_Bundle_Model_Selection and use the Mage_Bundle_Model_Resource_Selection resource model to access their data from table catalog_product_bundle_selection. The selection model has the following properties:
- selection_id – a unique identifier;
- option_id – reference to the option it belongs to;
- parent_product_id – reference to the bundle product;
- product_id – ID of the product represented by the selection;
- position – sorting attribute;
- is_default – flag marking the default selection, which an option can have only one;
- selection_price_value – price surcharge on the selection, which can be set only if the bundle has a fixed price model;
- selection_qty – quantity in which the selection product will be added to cart when it is chosen by a customer;
- selection_can_change_qty – flag enabling customers to enter their own quantity for the selection, which is possible only for options of “drop-down” or “radio button” type.
If you have multiple websites and want price surcharges to differ in each of them, make sure that the Magento setting catalog/price/scope is set to Website. This will allow supplying website-varying prices for bundle selections. These prices will then be stored in table catalog_product_bundle_selection_price.
The Magento core implements the option-selection relationship in such a way that for most practical reasons you have to load an option collection first and then extend it with selection items. Consider the following example:
public function getOptions() { if (!$this->_options) { $product = $this->getProduct(); $typeInstance = $product->getTypeInstance(true); $typeInstance->setStoreFilter($product->getStoreId(), $product); $optionCollection = $typeInstance->getOptionsCollection($product); $selectionCollection = $typeInstance->getSelectionsCollection( $typeInstance->getOptionsIds($product), $product ); $this->_options = $optionCollection->appendSelections($selectionCollection, false, Mage::helper('catalog/product')->getSkipSaleableCheck() ); } return $this->_options; }
Listing 6. Loading bundle options in a block class, /app/code/code/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle.php, line 47
Here an object of the Bundle product type is instantiated and its method’s getOptionsCollection is called, which returns a collection of the bundle product’s options. In the next line the getSelectionsCollection is called producing a collection of selection products associated with this bundle. Finally, the option collection’s method appendSelections adds the selection products to their respective options. After that each item of the option collection gets a property selections containing an array of Mage_Catalog_Model_Product objects.
This pattern is repeated on several other occasions:
- Mage_Bundle_Model_Product_Type::_prepareProduct, line 558-602 and line 623;
- Mage_Bundle_Model_Product_Price::getOptions, line 345;
- Mage_Bundle_Model_Observer::duplicateProduct, line 204-210;
- Mage_Bundle_Helper_Catalog_Product_Configuration::getBundleOptions, line 99-109.
Bundle product type class
Like other product types, the Bundle product type implements its custom functionality in an extension of the Mage_Catalog_Model_Product_Type_Abstract class. The Bundle type class is Mage_Bundle_Model_Product_Type and it contains methods that perform bundle-specific tasks in the following categories:
- providing access to bundle child elements: options and selections;
- saving product data to make sure that bundle information is written correctly to the database along with other product data;
- performing validity checks on buying requests containing bundle information.
Functions of the first category are rather trivial. Their names are self-explanatory and simply looking at their code reveals what they do:
- getChildrenIds,
- getOptions,
- getOptionsCollection,
- getOptionsIds,
- getOptionsByIds,
- getForceChildItemQtyChanges,
- getParentIdsByChild,
- getProductsToPurchaseByReqGroups,
- getRelationInfo,
- getSelectionsByIds,
- getSelectionsCollection,
- hasOptions.
Methods responsible for saving bundle data are a bit more complex:
- beforeSave – this method is a part of the process that is started before product data are written to a database. When a bundle product is about to be saved, the system calls the product model class’ function Mage_Catalog_Model_Product::_beforeSave. In its first lines a product type object is instantiated. Since it is a bundle the type’s class is Mage_Bundle_Model_Product_Type. The product type object calls its function beforeSave:
protected function _beforeSave() { $this->cleanCache(); $this->setTypeHasOptions(false); $this->setTypeHasRequiredOptions(false); $this->getTypeInstance(true)->beforeSave($this); /* code omitted for brevity */ }
Listing 7. Calling the type instance’s beforeSave() method, /app/code/core/Mage/Catalog/Model/Product.php, line 463.
What does beforeSave() do? First, it makes sure that no invalid data is saved. If a bundle product has a dynamic weight it can’t have a value for its weight attribute: it is calculated dynamically from the configured options. Similarly, if a bundle price is set to dynamic such attributes as msrp and msrp_display_actual_price_type (“msrp” stands for “merchant suggested retail price”) make no sense for the bundle itself and must be unset. Also in this method the system checks if the product has at least one valid option, and whether any option is required. In such cases it respectively sets product attributes type_has_options and type_has_required_options to true.
- save – this method is called at the end of the product saving routine from the product model’s _afterSave method:
protected function _afterSave() { $this->getLinkInstance()->saveProductRelations($this); $this->getTypeInstance(true)->save($this); /* code omitted for brevity */ }
Listing 8. Calling the type instance’s save() method, /app/code/core/Mage/Catalog/Model/Product.php, line 537.
The third group (adding bundles to cart or wishlist) is comprised of the following functions:
- checkProductBuyState – this function checks if a product can be sold. This check is performed on a quote item’s product whenever it is added or updated in the quote. The Bundle type’s override class adds the following checks:
- A buying request must contain a bundle_option parameter that contains configured options data.
- At least one of the selection items must be saleable or skipping saleable check must be explicitly set (which it is not by default).
- All required bundle options must be configured.
- _prepareProduct – the purpose of this method is to convert a buying request containing a bundle product and its configured options into quote items or to prepare a bundle product to be added to a wishlist. The conversion procedure is as follows:
- Read the options configuration from the request. If it is an adding-to-cart request and no options are configured – stop and return an error. Adding a product to a wishlist doesn’t require options to be configured (process mode “lite”) so the process can continue.
- Check if the product has any required bundle options and if they are configured. If not, return an error.
- If the required options are configured but their selection products are not saleable, return an error.
- For every selection product instantiate its product type instance and call its _prepareProduct method. If the selection item misses any required configuration this call will return a string with an error message. In this case the further processing is stopped and the error string is returned. If everything is OK, the returned value will be an array with one element: a product object of class Mage_Catalog_Model_Product. This object will receive properties necessary to be converted into a quote item. One of this properties is the parent_product_id, which is the ID of the bundle product.
- The Mage_Bundle_Model_Product_Type::_prepareProduct method returns a prepared array of the selection products and the bundle product objects to a quote that converts them into quote items.
Besides these three main method groups there are two functions that deserve our attention:
- getSku – this method is extended by the Bundle type in order to implement the dynamic bundle sku type.
public function getSku($product = null) { $sku = parent::getSku($product); if ($this->getProduct($product)->getData('sku_type')) { return $sku; } else { $skuParts = array($sku); if ($this->getProduct($product)->hasCustomOptions()) { $customOption = $this->getProduct($product)->getCustomOption('bundle_selection_ids'); $selectionIds = unserialize($customOption->getValue()); if (!empty($selectionIds)) { $selections = $this->getSelectionsByIds($selectionIds, $product); foreach ($selections->getItems() as $selection) { $skuParts[] = $selection->getSku(); } } } return implode('-', $skuParts); } }
Listing 9. Dynamic SKU generation by the Bundle product type, /app/code/core/Mage/Bundle/Model/Product/Type.php,* line 140.If the bundle product SKU type is dynamic, then the condition ($this->getProduct($product)->getData('sku_type')) would fail and the system will check if the product has a custom options bundle_selection_id. This option is normally set after a customer has configured a bundle and is available to product objects belonging to quote items. The bundle_selection_id is unserialized into an array of selection IDs. The system then fetches product objects associated with those selections and combines their SKU values with the bundle SKU into a single string.
- getWeight – this method implements the dynamic calculation of bundle weight. It follows the same principle as the getSku function: if the bundle weight type is dynamic get the bundle_selection_ids property, extract selection product objects from it, and calculate the total weight of the configured bundle product.
Conclusion
With this overview of the Mage_Bundle_Model_Product_Type class I conclude the “theoretical” part of our discussion on Magento’s Bundle product type. This type due to its flexibility can be effectively employed in many scenarios including product configurators, custom offer builders, combination deals, etc. In the next post I will present a walk-through of building a Magento extension that utilizes some of the Bundle product type features.
Great tutorial…
How can I render the bundle options in product list.
Is it possible by referencing the product.info.bundle
in the catalog_category_view handle or adding a product type bundle handle to the update_layout.
shoud i need to create a module with a controller
having methods of product and category controller
Thanks for this article about bundle products. It’s very instructive.
A description of the database tables could be nice also. But perhaps it’s too “low level”
How can i display weight for bundle product. like price same way want to reload weight based on configuration any help?
no help
Oleg, these are awesome posts this site has been a great go-to for a lot of my Magento work. To return the favor, you should add some social sharing options to your posts, would love to share this out on G+ or FB and it’ll help you SEO
Hi Dan,
I’m happy you like my blog. Thank you for the tip about the sharing buttons!
Thanks this was great info and you sound like you might be able to help with a bundled product issue I can’t find help on anywhere in my searches.
Basically I want the bundled product have a fixed price with a pick list of individual simple products in a bundle that need to be capped in the bundle to have a max quantity, but allow the user to “build their own” bundle. pick any 12 items
For example, you order a dozen donuts online. The bundle has a quantity of 15 simple products (individual donuts flavors). The customer might choose 2 glazed, 3 creme filled, etc., until they reach the bundled quantity of 12 and if they add 13 and try to add it to cart they will get an error message saying they have picked to many and need to remove 1. Anything you know of to help fix this issue?
Thanks!
Hi,
How to edit Bundle products programmatically.
Remove some options(simple products), Also add some new options(simple product ids)
Please help me….
Thanks in advance !!!!
Hi, i always come to your site and i find very useful info about magento.
Last week i spent dozens of hours trying to find a solution for a problem:
the owner wants that when he creates a new bundle product, it has set taxable goods as default. To do that, the Price Type must be set to “fixed”.
However, the default value in price type to new bundle items is “dynamic”.
I managed to trace the code to /app/code/core/Mage/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php
The dropdown is created under the getElementHtml() function (line 55 or so), but i cant find the way to default the fixed option.
In the other hand, i dont know if i finally get to default “fixed” automatically trigger the taxable goods.
Th store owner is always adding new bundle products, so defaulting taxable goods in price will save a lot of clicks for him.
Thanks in advance.
Norberto
Hello, I’m trying to figure out how I would get Magento to use to the SKU of the bundled product options rather than the product title. I need this because the way that our clients inventory is setup, a simple product doesn’t allow for the proper quickbooks integration. A bundled product would work perfectly for them if it referenced the SKUs and not the product names. Is this possible to do?
Need to get the Bundle products working on our site… The “Name” attribute it pulls for the options is to long so I want to use a dedicated attribute in the product setup to show instead, is this possible 🙂
My bundled product with dynamic pricing and a special price setting works fine except for one thing – the original price is not displayed with a strikethrough – neither on the category listing page nor on the product detail page. Only the special price is displayed. For simple products the original price with strikethrough is being displayed, just not on bundled products.
you can see it it here:
http://dev.fishvish.com/combo.html
any help would be great
Thanks for sharing this guide, It will help readers to know about the types of bundle products, But I was configuring it on Magento 2, https://www.cloudways.com/blog/magento-2-bundle-products/ and found this post very helpful.