Jump to content

Switch statement taking a long time to execute


DeX

Recommended Posts

I'll lay out my whole issue here just in case I'm incorrect about where the problem is.

 

I wrote some software in PHP to build a quote for a building company. I have a manual quantity calculation for every product they have in inventory and each time they modify the inputs, like the building dimensions, it recreates the quote and spits out a new price based on the quantity calculations for each individual product being used.

 

There are a number of inputs on the page and I make an AJAX call to the server to recreate the quote, then display the new quote once the AJAX return is done. This includes running all the quantity calculations.

 

When requesting the quantities, it goes into a switch statement consisting of 300 cases until it finds the matching ID of the product being requested. It then runs the quantity calculation I have coded in there which may or may not rely on quantities of other products in the system as well.

 

I have statements at the top and bottom of the switch statement to check an overall array to see if that item ID is already in the overall array (if the quantity has been calculated already). If so, it returns that quantity instead of recalculating it. If not, at the bottom of the switch statement it adds that quantity to the overall array in case that product quantity is requested again.

 

Here's how it looks:

function getQuantityBuildingProduct($productId, $showInsulation, $showPerma)
            {
                $quantities = array();
                
                if (isset($this->overallQuantities[$productId]))
                    return $this->overallQuantities[$productId];
                
                switch($productId)
                {
                    case ($this->cornerMoulding) :
                        $quantity = 0;

                        if ($this->isInsulated() || $showInsulation)
                        {
                            $quantity += ceil(
                                $this->getTotalQuantityBuildingProduct($this->threeByThreeFixedWindow, $showInsulation, $showPerma) * 1.5 +
                                $this->getTotalQuantityBuildingProduct($this->fourByThreeFixedWindow, $showInsulation, $showPerma) * 1.5;
                            );

                            $quantity += $this->getTotalQuantityBuildingProduct($this->threeFootStandardDoor, $showInsulation, $showPerma) * 2.5;
                        }

                        $quantities[] = ceil($quantity);
                        break;
                    case ($this->smokeDamper) :
                        $quantities[] = ceil(($this->getTotalQuantityBuildingProduct($this->smokeStop, $showInsulation, $showPerma) + $this->getTotalQuantityBuildingProduct($this->fireStop, $showInsulation, $showPerma)) * 3);
                        break;
                    case ($this->tuckTape) :
                        $quantities[] = ceil($this->getTotalQuantityBuildingProduct($this->poly, $showInsulation, $showPerma) / 2000);
                        break;
                    case ($this->ceilingOnlyLabour) :
                        if ($this->isCeilingOnly())
                            $quantities[] = 1;
                        
                        break;
default:
break;
}
 
                
                $this->overallQuantities[$productId] = $quantities;
                
                return $quantities;
}

 

 

It's taking about 10 seconds to get the entire quote back from the server since I implemented these switch statements. I have others to also get the widths, heights and lengths of the products just like I get these quantities. I used to have a separate function to get every single quantity, width, length and height but that was a lot of individual functions so I found this easier to add new functions to. Is this the cause my the slowdown? How can I make it faster?

 

My dream goal is to somehow store all of the quantity calculations in the database but I have no idea how I can do that since they're all different and sometimes rely on quantities of other products. I think having them in the database may speed things up (or slow them down) but I'm just not sure how to accomplish that right now. For now I'd just like to speed up the current way.

Link to comment
Share on other sites

Is it running a query to the db for each of these getTotalQuantityBuildingProduct()?  Do you have indexes on the columns you are gathering info from on those columns?  What kind of query does this getTotalQuantityBuildingProduct() run? 

Edited by fastsol
Link to comment
Share on other sites

function getTotalQuantityBuildingProduct($productId, $showInsulation, $showPerma)
            {
                $quantity = 0;
                
                foreach ($this->getQuantityBuildingProduct($productId, $showInsulation, $showPerma) as $productQuantity)
                    $quantity += $productQuantity;
                
                return $quantity;
            }

The getQuantityBuildingProduct() is the method I already posted. Since a product can have multiple quantities (3 doors at one width and 5 doors at a different width), the getQuantityBuildingProduct is an array of the quantity groups. The getTotalQuantityBuildingProduct simply adds together all the quantities from the array and gets a total. That total is multiplied by the price for a total cost of that type of product.......for most products. Some products use the widths and lengths to calculate the final cost as well if it is dependent on price per square foot (tiles, poly or sheets of metal).

Link to comment
Share on other sites

Is it the same amount of time for stuff at the top of the switch (corner moulding) than at the bottom (ceiling-only labor)?

 

Use microtime(true) to see how long things take, like

$start = microtime(true);
switch($productId)
{
...
}
$end = microtime(true);
error_log("switch took " . ceil(($end - $start) / 1024) . "ms");
Another thing is to install XDebug and use profiling: it'll measure how long everything takes, dump everything to a file, then you open the file with a program that reads "cachegrind" output and look through to see where the time is going.
Link to comment
Share on other sites

what was the performance of your code when using functions (i'll assume you were calling the appropriate function dynamically using variable functions?)?

 

using a class->property as the case x: value, is not going to be as efficient as using a fixed value. i would use a defined constant as the case values. the same would be true for all the other class->properties you are showing in the code. you have essentially created a different variable/property, who's name indicates the purpose of the variable, when in fact nothing about the usage is variable.

Link to comment
Share on other sites

i've been looking at your code posted in this thread, and it appears that the rules in the code are for calculating the amount of a sub-item by getting the quantity of each relevant main item that uses that sub-item and putting that quantity through the rule in this code. also, despite storing the quantity for each productId as an array within the main array, the code in this thread is only storing ONE array element, with the total quantity.

 

this means you are calling the getQuantityBuildingProduct() function for each sub-item that a main item uses, each time the main item quantity gets changed (unless you are calling the function for EVERY POSSIBLE sub-item, every time ANY main item quantity changes, in which case i can see why this would take 10 seconds). this requires that the code (or data) for each main item contain a list of sub-items and your code in this thread requires the correct rule for each sub-item.  so, you have two separate places in the code that must be kept up to date with any new or changed relationship between main and sub-items.

 

i suggest, instead, just using the list of sub-items, located in the main item code (or data), to call the appropriate rule for each sub-item and update the sub-item quantity at the time you update a main item quantity.

 

you are storing data like this -

overallQuantities[main_item_id][] = quantity // main items
overallQuantities[sub_item_id][] = quantity // sub items

if you add the main_item_id as an array index for the sub-items, you can find and update the corresponding quantity of a sub-item when the quantity of the main item changes -

overallQuantities[sub_item_id][main_item_id] = quantity

for example -

overallQuantities[cornerMoulding][threeByThreeFixedWindow] = x quantity
overallQuantities[cornerMoulding][threeFootStandardDoor] = y quantity

your getTotalQuantityBuildingProduct() function will work with this structure (it's actually getting a single element array now, but would be getting an array of arrays, if a sub-item is used with more than one main item.)  you can also replace the foreach() loop in the getTotalQuantityBuildingProduct() function with an array_sum() call, both with your current scheme and with the one i have suggested.

 

this will also allow you to produce a cross-reference listing of the sub-items showing what amount of them are used for each different main item -

cornerMoulding -
    threeByThreeFixedWindow - x quantity
    threeFootStandardDoor - y quantity

edit: and in thinking about your use of the $this->overallQuantities[$productId] array to cache the amounts, if you change a quantity of a main item, i.e. the number of a type of window, your code won't recalculate the amount of a sub-item, i.e. the corner molding, if the sub-item amount is already in the array, which it likely would be, unless you have even more code that we haven't seen to take care of this common occrance.

Edited by mac_gyver
Link to comment
Share on other sites

Is it the same amount of time for stuff at the top of the switch (corner moulding) than at the bottom (ceiling-only labor)?

 

 

Every time an input box is edited on the quote page, the pricing for the entire job is recalculated, that means recalculating every quantity of every product, building the HTML for the quote and displaying it on the page using AJAX. So if the customer wants 10 gable stops instead of 5, you make that change and the program recalculates every single quantity of every product, every length, height, width, recalculates the cost of that job and displays it. It's taking about 10 seconds where before it was almost instant with individual functions for each call. I'm actually getting complains from the sales guys that it's taking so long to update now after a change.

So to answer your question, there is no time when I would only be calling corner moulding or ceiling-only labour, every single building product is recalculated and called on any edit. When the sales guys are sitting with a customer, the customer likes to change 10-20 different things to try and get the price within their range. They might add a window and that will change the quantities of 5 or 6 different lumber products used in framing the window, it'll remove quantities of exterior metal and add window labour costs so the entire building needs to be recalculated when this change is made.

 

 

what was the performance of your code when using functions (i'll assume you were calling the appropriate function dynamically using variable functions?)?

 

using a class->property as the case x: value, is not going to be as efficient as using a fixed value. i would use a defined constant as the case values. the same would be true for all the other class->properties you are showing in the code. you have essentially created a different variable/property, who's name indicates the purpose of the variable, when in fact nothing about the usage is variable.

 

No, I was not using variable functions, that was the reason for the change. I had actual functions created and named for every single item quantity, width, length, height, unit-price and total price. It was a huge pain adding a new product and entering functions for every dimension calculation, then adding that to the total price of the building price calculation. That's a good idea to change all the items to constants though, I'll try doing that. Right now I have all the building products in the database with a row ID and then defined in the program like so:

        private $lumber2x8x12Treated = 14;
	private $lumber2x8x16Treated = 15;
	private $lumber2x6x12 = 16;
	private $lumber2x6x16 = 17;
	private $lumber2x8x12 = 18;
	private $lumber2x8x16 = 19;
	private $lumber2x4x12 = 20;
	private $lumber2x4x16 = 21;
	private $lumber2x10x16 = 23;
	private $lumber2x6x16TreatedFillerBoard = 24;
	private $lumber2x6x12TreatedFillerBoard = 25;
	private $lumber2x8x16TreatedFillerBoard = 26;
	private $lumber2x8x12TreatedFillerBoard = 27;
	private $lumber2x6x12Treated = 32;
	private $lumber2x6x16Treated = 33;
	private $wallMetal = 34;
	private $gableMetal = 42;
	private $roofMetal = 43;
	private $wallScrews = 44;
	private $trimScrews = 46;
	private $heelMetal = 47;
	private $biFoldDoorMetal = 48;
	private $slidingDoorMetal = 49;
	private $exteriorBaseTrim = 50;
	private $birdStop = 51;
	private $ridgeCap = 52;
	private $ridgeLight = 53;

As for the post right above this one, I'll reply to that once I read it again and figure it out a bit better, I just ran through it quickly but I'm heading to bed here and need some more time to read through it a bit better. I will reply to it when I get a chance.

Link to comment
Share on other sites

i've been looking at your code posted in this thread, and it appears that the rules in the code are for calculating the amount of a sub-item by getting the quantity of each relevant main item that uses that sub-item and putting that quantity through the rule in this code. also, despite storing the quantity for each productId as an array within the main array, the code in this thread is only storing ONE array element, with the total quantity.

 

this means you are calling the getQuantityBuildingProduct() function for each sub-item that a main item uses, each time the main item quantity gets changed (unless you are calling the function for EVERY POSSIBLE sub-item, every time ANY main item quantity changes, in which case i can see why this would take 10 seconds). this requires that the code (or data) for each main item contain a list of sub-items and your code in this thread requires the correct rule for each sub-item.  so, you have two separate places in the code that must be kept up to date with any new or changed relationship between main and sub-items.

 

i suggest, instead, just using the list of sub-items, located in the main item code (or data), to call the appropriate rule for each sub-item and update the sub-item quantity at the time you update a main item quantity.

 

you are storing data like this -

overallQuantities[main_item_id][] = quantity // main items
overallQuantities[sub_item_id][] = quantity // sub items

if you add the main_item_id as an array index for the sub-items, you can find and update the corresponding quantity of a sub-item when the quantity of the main item changes -

overallQuantities[sub_item_id][main_item_id] = quantity

for example -

overallQuantities[cornerMoulding][threeByThreeFixedWindow] = x quantity
overallQuantities[cornerMoulding][threeFootStandardDoor] = y quantity

your getTotalQuantityBuildingProduct() function will work with this structure (it's actually getting a single element array now, but would be getting an array of arrays, if a sub-item is used with more than one main item.)  you can also replace the foreach() loop in the getTotalQuantityBuildingProduct() function with an array_sum() call, both with your current scheme and with the one i have suggested.

 

this will also allow you to produce a cross-reference listing of the sub-items showing what amount of them are used for each different main item -

cornerMoulding -
    threeByThreeFixedWindow - x quantity
    threeFootStandardDoor - y quantity

edit: and in thinking about your use of the $this->overallQuantities[$productId] array to cache the amounts, if you change a quantity of a main item, i.e. the number of a type of window, your code won't recalculate the amount of a sub-item, i.e. the corner molding, if the sub-item amount is already in the array, which it likely would be, unless you have even more code that we haven't seen to take care of this common occrance.

 

As far as I can understand what you're saying, I may have explained it incorrectly. The quote page has about 60 different inputs for changing things like:

- building width

- building height

- building length

- building type (residential, commercial, barn, hayshed....)

- insulated or not

- how many windows?

- how many doors?

- what's the roof pitch?

 

The sales guy will sit down with a customer and enter in all that information and right next to the input boxes there is a live quote which displays the actual live price for those options. That price is calculated by running through the quantities of every single available building product (300 of them), getting the unit price and finally getting a final cost for that job, down to the penny. It's so accurate that I know that building will require 14,394 screws to complete, not 1 more, not 1 fewer. So I don't think I can do it your way (please tell me if I'm wrong) because I don't have parent items and sub-items, I just have inputs. If the sales guy makes the building 10 feet longer, the program needs to recalculate every quantity because that might change the quantities, lengths, widths and heights of 20-30 different items (lumbers, metals, trims, posts, more labour.....). So they're not just choosing to add more corner mouldings, they're changing the building dimensions and that is getting a new quantity for corner mouldings on the back end.

Link to comment
Share on other sites

we understand what you are doing and how you are doing it. the problem is, your current code is taking a long time to run and all the hard-coded logic and hard-code properties in the code are a maintenance problem.

 

from what i gather, to recalculate the bom/price, upon each change being input, you are currently calling the code for every possible building productId (~300 times), to get each id to calculate how much of it is needed based on the quantity of what it is used for. this also requires that you call the code with the product id's in the proper order. for example, if you haven't called the code to update the cornerMoulding quantity yet, any product id that is used for cornerMoulding, such as fasteners, won't produce the correct quantity.

 

this is what i would call a bottom-up approach. my suggestion earlier in the thread is a top-down approach, where each change only causes the items affected by that change to be recalculated.

 

for the performance problem, if the function method produced acceptable performance, you would want to continue using that method.

 

for the code maintenance problem (top-down or bottom-up approach), you need a data driven design, where you define the rules in a data structure (an array for now, a database table later) that tells the code what to do.

 

for the information you have shown in this thread (bottom-up approach), the following pseudo/non-functional code demonstrates a data driven design -

// define the rules that tell the code what to do
$items = array();
$items[] = array(threeByThreeFixedWindow,1.5); // id, individual multiplier
$items[] = array(fourByThreeFixedWindow,1.5); // id, individual multiplier
$items[] = array(threeFootStandardDoor,2.5); // id, individual multiplier        
$rules[cornerMoulding] = array($items,1,'ceil'); // array of item(s), group multiplier, operator
        
$items = array();
$items[] = array(smokeStop,1); // id, individual multiplier
$items[] = array(fireStop,1); // id, individual multiplier
$rules[smokeDamper] = array($items, 3,'ceil'); // array of item(s), group multiplier, operator
    
$items = array();
$items[] = array(poly,1); // id, individual multiplier
$rules[tuckTape] = array($items, 1/2000, 'ceil'); // array of item(s), group multiplier, operator
// a function that knows how to apply the rules
function _calc($rule){
    $qty = 0;
    foreach($rule[0] as $item){
        $qty += $this->getTotalQuantityBuildingProduct($item[0], $showInsulation, $showPerma) * $item[1]; // get the total for the productId and apply the individual multiplier
    }
    $qty *= $rule[1]; // apply group multiplier
    if($rule[2] == ''){
        return $qty;
    } else {
        return $rule[2]($qty); // use variable functions to apply the operator
    }
}
// example usage -
$quantities[] = _calc($rules[smokeDamper]); // call this to use the defined $rules for the productId, instead of the hard-code program logic
Edited by mac_gyver
Link to comment
Share on other sites

 

we understand what you are doing and how you are doing it. the problem is, your current code is taking a long time to run and all the hard-coded logic and hard-code properties in the code are a maintenance problem.

 

from what i gather, to recalculate the bom/price, upon each change being input, you are currently calling the code for every possible building productId (~300 times), to get each id to calculate how much of it is needed based on the quantity of what it is used for. this also requires that you call the code with the product id's in the proper order. for example, if you haven't called the code to update the cornerMoulding quantity yet, any product id that is used for cornerMoulding, such as fasteners, won't produce the correct quantity.

 

this is what i would call a bottom-up approach. my suggestion earlier in the thread is a top-down approach, where each change only causes the items affected by that change to be recalculated.

 

for the performance problem, if the function method produced acceptable performance, you would want to continue using that method.

 

for the code maintenance problem (top-down or bottom-up approach), you need a data driven design, where you define the rules in a data structure (an array for now, a database table later) that tells the code what to do.

 

for the information you have shown in this thread (bottom-up approach), the following pseudo/non-functional code demonstrates a data driven design -

// define the rules that tell the code what to do
$items = array();
$items[] = array(threeByThreeFixedWindow,1.5); // id, individual multiplier
$items[] = array(fourByThreeFixedWindow,1.5); // id, individual multiplier
$items[] = array(threeFootStandardDoor,2.5); // id, individual multiplier        
$rules[cornerMoulding] = array($items,1,'ceil'); // array of item(s), group multiplier, operator
        
$items = array();
$items[] = array(smokeStop,1); // id, individual multiplier
$items[] = array(fireStop,1); // id, individual multiplier
$rules[smokeDamper] = array($items, 3,'ceil'); // array of item(s), group multiplier, operator
    
$items = array();
$items[] = array(poly,1); // id, individual multiplier
$rules[tuckTape] = array($items, 1/2000, 'ceil'); // array of item(s), group multiplier, operator
// a function that knows how to apply the rules
function _calc($rule){
    $qty = 0;
    foreach($rule[0] as $item){
        $qty += $this->getTotalQuantityBuildingProduct($item[0], $showInsulation, $showPerma) * $item[1]; // get the total for the productId and apply the individual multiplier
    }
    $qty *= $rule[1]; // apply group multiplier
    if($rule[2] == ''){
        return $qty;
    } else {
        return $rule[2]($qty); // use variable functions to apply the operator
    }
}
// example usage -
$quantities[] = _calc($rules[smokeDamper]); // call this to use the defined $rules for the productId, instead of the hard-code program logic

 

 

I don't know how to split your quote so I'll answer it in 2 parts here.

As per your question in the first part, you're close but it doesn't quite work like that. I'm not updating quantities anywhere, I'm dynamically calculating the quantities each time they're requested. Let's use the following example:

- quantityScrews = buildingWidth * 2;

- quantity2x4 = quantityScrews / 3;

- quantitySoffit = (quantityScrews > 100 ? quantity2x4 * 5 : quantityScrews + 4;

 

So when a change is made in the program, I request the quantity of screws and it gets stored into the overallQuantities array for no other reason but to speed up the subsequent requests. That array isn't a master quantity array, it's just a quicker place to get the quantity instead of having to recalculate it again. So when I request the quantity of 2x4, it'll get quantityScrews from the overallQuantities array which (I assume) is faster than recalculating it again. I could be wrong but it sounds like you think I'm using that array as a quantity storage, then updating the quantities in there as needed, that's not the case. Should I be?

The main calculation happening here is getFinalCost() which makes a request to get the total costs of all the individual products which makes a request to get all the quantities of each product. Those quantities are requested from the switch statement every time.

 

On to the second part of your reply, the code you posted. I think you may really be on to something here but I don't quite understand it well enough to implement it. So let's say the sales guy changes the building to be 10 feet wider, the building width is used as a calculation in around 50% of the product quantity calculations so that's going to have a huge effect on the quantities of everything. More lumber, more posts, more metal, trims, insulation, everything. Also, you don't always have simple operators being applied, sometimes you have complicated IF statements like the quantitySoffit calculations above. Will that still work in your example? It looks like what you posted may work so I'd really like to understand it a bit better, please let me know if this is correct:

- I would create a set of rules for all 300 products like you did for the 3 you posted above.

- Then when the building length is changed, I would call the _calc method? Is that right?

- What is the $rule variable I pass into _calc?

 

I really like your top down idea but here are 2 more quantity calculations which I can't quite figure out how to convert to your example:

 

This one is for interiorJTrim, it isn't too bad. There is an IF statement to see if the building is insulated because they're only used on insulated buildings. The $showInsulation variable is used to force an insulated price, I use that to get the price of the building if it were to be insulated, that allows the sales guys to print a cost on the quote for upgrading to an insulated building should the customer change their mind later. I can use that to query an insulated cost of their building by passing that value as true. You'll notice I changed all the product variables to constants but it didn't speed anything up.

                    case (self::interiorJTrim) :
                        $quantity = 0;

                        if ($this->isInsulated() || $showInsulation)
                        {
                            $quantity +=
                                $this->getTotalQuantityManDoors($showInsulation, $showPerma) * 2.5 +
                                $this->getTotalQuantityBuildingProduct(self::threeByThreeFixedWindow, $showInsulation, $showPerma) * 1.5 +
                                $this->getTotalQuantityBuildingProduct(self::fourByThreeFixedWindow, $showInsulation, $showPerma) * 1.5 +
                                $this->getTotalQuantityBuildingProduct(self::threeByThreeSlidingWindow, $showInsulation, $showPerma) * 2 +
                                $this->getTotalQuantityBuildingProduct(self::fourByThreeSlidingWindow, $showInsulation, $showPerma) * 2 +
                                $this->getQuantityCustomWindows() * 3 +
                                $this->getTotalOverheadDoorQuantity() * 2 +
                                $this->getTotalHeightBuildingProduct(self::biFoldDoor, $showInsulation, $showPerma) * 2 / 9 +
                                $this->getTotalWidthBuildingProduct(self::biFoldDoor, $showInsulation, $showPerma) / 9 +
                                $this->getTotalQuantityBuildingProduct(self::highLiftCharge, $showInsulation, $showPerma) * 1 +
                                $this->getTotalQuantityBuildingProduct(self::atticHatch, $showInsulation, $showPerma) * 2;
                        }

                        $quantities[] = ceil($quantity);
                        break;

Now for the worst one, this is the quantity of wallMetal, it's probably the most complicated quantity calculation in the system:

 

- First get a value based on all the overhead, sliding and bi-fold doors the sales guy added.

- Then if it doesn't have exterior wall light, add more to the value.

- Then generate a quantity of wall metal by REMOVING the value we just calculated because we need to remove any wall metal which will no longer be in the places of doors and wall light. Obviously those holes in the wall will now be filled with doors and wall light instead of wall metal. We don't want to charge the customer for that wall metal if we're not using it.

- Then we calculate another quantity to add to the quantity array. The reason we're separating the quantities is because this quantity group of wall metal will have a different length. It's the same product in the system (wall metal) but when it gets printed out on the product list for the building construction, we need to know how many pieces of wall metal we have for each length. That length is calculated separately in the getLengthBuildingProduct method which is another switch statement.

                    case (self::wallMetal) :
                        $quantity = 0;
                        $value = 0;

                        for ($i = 0; $i < count(explode(",", $this->overheadQuantities, -1)); $i++)
                            if (!$this->hasExteriorWallLight()) // does not have wall light
                                $value += (floor($this->getOverheadDoorWidths($i) / 3) - 2) * $this->getOverheadDoorQuantities($i);

                        for ($i = 0; $i < count(explode(",", $this->slidingQuantities, -1)); $i++)
                            if (!$this->hasExteriorWallLight()) // does not have wall light
                                $value += (floor($this->getSlidingDoorWidths($i) / 3) - 2) * $this->getSlidingDoorQuantities($i);

                        for ($i = 0; $i < count(explode(",", $this->biFoldQuantities, -1)); $i++)
                            if (!$this->hasExteriorWallLight()) // does not have wall light
                                $value += (floor($this->getBiFoldDoorWidths($i) / 3) - 2) * $this->getBiFoldDoorQuantities($i);

                        if (!$this->hasExteriorWallLight()) // if it doesn't have exterior wall light
                            $value += $this->getTotalQuantityBuildingProduct(self::eveAccent, $showInsulation, $showPerma) + $this->getTotalQuantityBuildingProduct(self::gableAccent, $showInsulation, $showPerma);

                        $quantities[] = (ceil($this->getBuildingWidth() / 3) + ceil($this->getBuildingLength() / 3)) * 2 + 1 - floor($this->getLengthExteriorWallLight() / 3) - $value;
                        
                        $quantity = 0;
                        $value = 0;

                        for ($i = 0; $i < count(explode(",", $this->overheadQuantities, -1)); $i++)
                            if ($this->hasExteriorWallLight()) // has wall light
                                $value += (floor($this->getOverheadDoorWidths($i) / 3) - 2) * $this->getOverheadDoorQuantities($i);

                        for ($i = 0; $i < count(explode(",", $this->slidingQuantities, -1)); $i++)
                            if ($this->hasExteriorWallLight()) // has wall light
                                $value += (floor($this->getSlidingDoorWidths($i) / 3) - 2) * $this->getSlidingDoorQuantities($i);

                        for ($i = 0; $i < count(explode(",", $this->biFoldQuantities, -1)); $i++)
                            if ($this->hasExteriorWallLight()) // has wall light
                                $value += (floor($this->getBiFoldDoorWidths($i) / 3) - 2) * $this->getBiFoldDoorQuantities($i);

                        $quantity += $this->getQuantityExteriorWallLight() - $value;

                        $quantityGableAccents = $this->getQuantityBuildingProduct(self::gableAccent, $showInsulation, $showPerma);
                        
                        if ($this->hasExteriorWallLight()) // if it has exterior wall light
                            $quantity -= $this->getTotalQuantityBuildingProduct(self::eveAccent, $showInsulation, $showPerma) + $quantityGableAccents[0];

                        $quantities[] = $quantity;
                        break;
Edited by DeX
Link to comment
Share on other sites

but it sounds like you think I'm using that array as a quantity storage, then updating the quantities in there as needed, that's not the case. Should I be?

 

 

i would say yes. i have done a lot of BOMs/estimating for industrial controls, at the $1.5M level for entire plants. you are building a bill of materials (somewhere). that bom needs the quantity of each direct item that has been added, and any follow-on/sub-items that are the result of each item, recursively down to the end of the chain. from a processing point of view, just do things once, when an input is added/deleted/changed, and only for those things that are in the bom.

 

this approach would be the same as an onchange event in a browser or a trigger in a database. some input event occurs, the code that runs, takes the rules that have been defined for that event and applies them as needed. if a window gets added/deleted, the rules defined for that window type tell the code what and what quantity of sub-items (such as the corner molding) need to be added/deleted from the bom, any of those sub-items will have rules that tell the code what and what quantity of sub-sub-items (such as fasteners) need to be added/deleted from the bom.

 

the array structure you have for the overallQuantities already has an array of arrays for each productId, though it looks like the code isn't using more than the first element of each array. the suggested usage of the [main_item_id] as the secondary array index, in reply #6, keeps each element in the array tied to what caused that quantity. if you change the quantity of the parent (delete a specific size window), you only have to calculate and update that corresponding one element for the child item (corner molding.) all the other quantities of that particular child item aren't touched.

 

because you have more than just two levels, what i posted in reply #6 would actually be recursive, and look like the following with as many levels as needed - 

overallQuantities[main_item_id] = quantity
overallQuantities[sub_item_id][main_item_id] = quantity
overallQuantities[sub_sub_item_id][sub_item_id][main_item_id] = quantity

for the insulated vs uninsulated pricing, i would maintain two boms, one for each case. the code would calculate quantities with and without insulation and store the results in separate boms (actually, i would just use an array with a main index that indicates insulated and uninsulated branches.)

 

for the _calc() function in my example. you can make as many different functions that you need for the different classes of rules. just call the correct one depending on the situation. in the list of rules, you can 'dynamically' call you own functions, using variable functions, by listing the function name somewhere in the rules. in my example, i am calling the ceil function using variable functions. your rule(s) could have function name, like i used, or even an array of function names that you loop over to dynamically call each function in turn.

Edited by mac_gyver
Link to comment
Share on other sites

i would say yes. i have done a lot of BOMs/estimating for industrial controls, at the $1.5M level for entire plants. you are building a bill of materials (somewhere). that bom needs the quantity of each direct item that has been added, and any follow-on/sub-items that are the result of each item, recursively down to the end of the chain. from a processing point of view, just do things once, when an input is added/deleted/changed, and only for those things that are in the bom.

 

this approach would be the same as an onchange event in a browser or a trigger in a database. some input event occurs, the code that runs, takes the rules that have been defined for that event and applies them as needed. if a window gets added/deleted, the rules defined for that window type tell the code what and what quantity of sub-items (such as the corner molding) need to be added/deleted from the bom, any of those sub-items will have rules that tell the code what and what quantity of sub-sub-items (such as fasteners) need to be added/deleted from the bom.

 

the array structure you have for the overallQuantities already has an array of arrays for each productId, though it looks like the code isn't using more than the first element of each array. the suggested usage of the [main_item_id] as the secondary array index, in reply #6, keeps each element in the array tied to what caused that quantity. if you change the quantity of the parent (delete a specific size window), you only have to calculate and update that corresponding one element for the child item (corner molding.) all the other quantities of that particular child item aren't touched.

 

because you have more than just two levels, what i posted in reply #6 would actually be recursive, and look like the following with as many levels as needed - 

overallQuantities[main_item_id] = quantity
overallQuantities[sub_item_id][main_item_id] = quantity
overallQuantities[sub_sub_item_id][sub_item_id][main_item_id] = quantity

for the insulated vs uninsulated pricing, i would maintain two boms, one for each case. the code would calculate quantities with and without insulation and store the results in separate boms (actually, i would just use an array with a main index that indicates insulated and uninsulated branches.)

 

for the _calc() function in my example. you can make as many different functions that you need for the different classes of rules. just call the correct one depending on the situation. in the list of rules, you can 'dynamically' call you own functions, using variable functions, by listing the function name somewhere in the rules. in my example, i am calling the ceil function using variable functions. your rule(s) could have function name, like i used, or even an array of function names that you loop over to dynamically call each function in turn.

I'm only using the first array element of overallQuantities for most elements but like the wallMetal example I posted above, some items have more than one quantity group because there are different quantities at different lengths. Like overhead doors, you might have 3 at 10x12 and then another 5 at 12x12. So then the quantities array would have 2 elements in it.

 

How would this work for getting the total price? If the building costs $300,000 and part of that is corner moulding, what is the new price when the quantity of corner moulding has changed? Am I keeping another array of the total prices for each product and tallying the total price of the building based on adding them all together? Then I would have to re-add them if one of the quantities change. Is that right?

 

So now I'm trying to understand what you're doing with the BOM because I really want to implement this in the system. So let me try and walk through an example here to see if I can figure out how this works.

 

overallQuantities[main_item_id] = quantity

overallQuantities[sub_item_id][main_item_id] = quantity

overallQuantities[sub_sub_item_id][sub_item_id][main_item_id] = quantity

 

This is the example you posted, if I'm making a change which will update the quantity of corner moulding, would the corner moulding be the sub-item or main-item in this example? Going from the beginning:

- an edit is done on the web page (building length changed)

- AJAX function is called

- AJAX function passes in the name of the changed element as well as the value of that element

- corner moulding quantity requires updating based on the changed element and its value

- overallQuantities[cornerMoulding] is updated using the huge quantity calculation assigned to corner mouldings

- overQuantities[cornerMoulding][whiteScrews] is updated using the quantity calulation assigned to white screws which uses corner mouldings. Is that right? Did I use the main and sub items correctly there?

- somehow the total price is updated and the new quote is displayed to the user.

 

There are also other things on the quote which change based in some inputs. For example, if the sales guy changes the post spacing (distance between posts) from 6 feet to 4 feet, that's going to have an effect on the quantity of posts but it's also going to show up in an area at the top of the quote which states what the post spacing is. This isn't for every element, just for a couple, but post spacing is one of them. So this text would need to change to reflect the new post spacing, as well as show the new total price of the quote.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.