| 1 | <?php  | 
            ||
| 2 | |||
| 3 | namespace P4lv\ExcelGoalSeek;  | 
            ||
| 4 | |||
| 5 | use P4lv\ExcelGoalSeek\Exception\ExcelGoalSeekException;  | 
            ||
| 6 | use P4lv\ExcelGoalSeek\Exception\GoalNeverReached;  | 
            ||
| 7 | use P4lv\ExcelGoalSeek\Exception\GoalReachedNotEnough;  | 
            ||
| 8 | use Psr\Log\LoggerInterface;  | 
            ||
| 9 | |||
| 10 | class ExcelGoalSeek  | 
            ||
| 11 | { | 
            ||
| 12 | private $debugEnabled;  | 
            ||
| 13 | /**  | 
            ||
| 14 | * @var LoggerInterface|null  | 
            ||
| 15 | */  | 
            ||
| 16 | private $logger;  | 
            ||
| 17 | |||
| 18 | 10 | public function __construct(LoggerInterface $logger = null)  | 
            |
| 19 |     { | 
            ||
| 20 | 10 | $this->logger = $logger;  | 
            |
| 21 | 10 | }  | 
            |
| 22 | 10 | ||
| 23 | private function debug($message, array $context = []): void  | 
            ||
| 24 | 9 |     { | 
            |
| 25 |         if ($this->debugEnabled instanceof LoggerInterface) { | 
            ||
| 26 | 9 | $this->logger->debug($message, $context);  | 
            |
| 
                                                                                                    
                         0 ignored issues 
                            –
                            show
                         | 
                |||
| 27 | 4 | }  | 
            |
| 28 | }  | 
            ||
| 29 | 9 | ||
| 30 | public function calculate(  | 
            ||
| 31 | 10 | $functionGS,  | 
            |
| 32 | $goal,  | 
            ||
| 33 | $decimal_places,  | 
            ||
| 34 | $incremental_modifier = 1,  | 
            ||
| 35 | $max_loops_round = 0,  | 
            ||
| 36 | $max_loops_dec = 0,  | 
            ||
| 37 | $lock_min = ['num' => null, 'goal' => null],  | 
            ||
| 38 | $lock_max = ['num' => null, 'goal' => null],  | 
            ||
| 39 | $slope = null,  | 
            ||
| 40 | $randomized = false,  | 
            ||
| 41 | $start_from = 0.1  | 
            ||
| 42 | )  | 
            ||
| 43 |     { | 
            ||
| 44 |         if (empty($functionGS)) { | 
            ||
| 45 | 10 |             throw new ExcelGoalSeekException('Function callback expected'); | 
            |
| 46 | 1 | }  | 
            |
| 47 | |||
| 48 | $max_loops_round++;  | 
            ||
| 49 | 9 | ||
| 50 | //If goal found has more than this difference, return null as it is not found  | 
            ||
| 51 | 9 | $maximum_acceptable_difference = 0.1;  | 
            |
| 52 | |||
| 53 | 9 |         if ($max_loops_round > 100) { | 
            |
| 54 | 1 | throw new GoalNeverReached();  | 
            |
| 55 | }  | 
            ||
| 56 | 1 | ||
| 57 |         $this->debug(sprintf("Iteration %d; min value = %s; max value = %s; slope %s", $max_loops_round, $lock_min['num'], $lock_max['num'], $slope)); | 
            ||
| 58 | |||
| 59 | 9 | //If I have the goal limited to a unit, I seek decimals  | 
            |
| 60 |         if ($lock_min['num'] !== null && $lock_max['num'] !== null && abs(abs($lock_max['num']) - abs($lock_min['num'])) <= 1) { | 
            ||
| 61 | |||
| 62 | 9 | //No decimal , return result  | 
            |
| 63 |             if ($lock_min['num'] == $lock_max['num']) { | 
            ||
| 64 | return $lock_min['num'];  | 
            ||
| 65 | 8 | }  | 
            |
| 66 | 2 | ||
| 67 | //Seek decimals  | 
            ||
| 68 |             foreach (range(1, $decimal_places, 1) as $decimal) { | 
            ||
| 69 | $decimal_step = 1 / (10 ** $decimal);  | 
            ||
| 70 | 6 | ||
| 71 | 6 | $difference = abs(round(abs($lock_max['num']), $decimal) - round(abs($lock_min['num']), $decimal));  | 
            |
| 72 | |||
| 73 | 6 |                 while ($difference - ($decimal_step / 10) > $decimal_step && $max_loops_dec < (2000 * $decimal_places)) { | 
            |
| 74 | $max_loops_dec++;  | 
            ||
| 75 | 6 | ||
| 76 | 6 | $aux_obj_num = round(($lock_min['num'] + $lock_max['num']) / 2, $decimal);  | 
            |
| 77 | $aux_obj = $this->$functionGS($aux_obj_num);  | 
            ||
| 78 | 6 | ||
| 79 | 6 |                     $this->debug(sprintf("Decimal iteration %d; min value = %s; max value = %s; value %s", $max_loops_dec, $lock_min['num'], $lock_max['num'], $aux_obj)); | 
            |
| 80 | |||
| 81 | 6 | //Like when I look without decimals  | 
            |
| 82 | 6 | [$lock_min, $lock_max] = $this->lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope);  | 
            |
| 83 | //End Like when I look without decimals  | 
            ||
| 84 | $difference = abs(round(abs($lock_max['num']), $decimal) - round(abs($lock_min['num']), $decimal));  | 
            ||
| 85 | }//End while  | 
            ||
| 86 | 6 | }//End foreach  | 
            |
| 87 | |||
| 88 | 6 | ||
| 89 |             if ($max_loops_dec > 2000 * $decimal_places) { | 
            ||
| 90 |                 throw new GoalNeverReached('Goal never reached [2000]'); | 
            ||
| 91 | }  | 
            ||
| 92 | |||
| 93 | 6 |             if (!is_nan($lock_min['goal']) && abs(abs($lock_min['goal']) - abs($goal)) < $maximum_acceptable_difference) { | 
            |
| 94 | return round($lock_min['num'], $decimal_places - 1);  | 
            ||
| 95 | }  | 
            ||
| 96 | |||
| 97 | 6 |             throw new GoalReachedNotEnough('Goal reached not enough'); | 
            |
| 98 | 6 | }  | 
            |
| 99 | |||
| 100 | //First iteration, try with zero  | 
            ||
| 101 | $aux_obj_num = $this->getAuxObjNum($lock_min['num'], $lock_max['num'], $start_from, $incremental_modifier);  | 
            ||
| 102 | |||
| 103 | $aux_obj = $this->$functionGS($aux_obj_num);  | 
            ||
| 104 | |||
| 105 | 9 |         $this->debug(sprintf("Testing (with initial value) %s%d with value %s", $aux_obj_num != $start_from ? '' : '(with initial value)', $aux_obj_num, $aux_obj)); | 
            |
| 106 | 9 | ||
| 107 |         if ($slope === null) { | 
            ||
| 108 | 9 | $aux_slope = $this->$functionGS($aux_obj_num + 0.1);  | 
            |
| 109 | 8 | ||
| 110 | 8 |             if (is_nan($aux_slope) || is_nan($aux_obj)) { | 
            |
| 111 | $slope = null; //If slope is null  | 
            ||
| 112 | 8 |             } elseif ($aux_slope - $aux_obj > 0) { | 
            |
| 113 | $slope = 1;  | 
            ||
| 114 |             } else { | 
            ||
| 115 | 9 | $slope = -1;  | 
            |
| 116 | 1 | }  | 
            |
| 117 | 1 | }  | 
            |
| 118 | |||
| 119 | 1 | //Test if formule can give me non valid values, i.e.: sqrt of negative value  | 
            |
| 120 |         if (!is_nan($aux_obj)) { | 
            ||
| 121 | //Is goal without decimals?  | 
            ||
| 122 | 8 | list($lock_min, $lock_max) = $this->lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope);  | 
            |
| 123 | 8 |         } else { | 
            |
| 124 |             if (($lock_min['num'] === null && $lock_max['num'] === null) || $randomized) { | 
            ||
| 125 | $nuevo_start_from = random_int(-500, 500);  | 
            ||
| 126 | 9 | ||
| 127 | 9 | return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier + 1, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, true, $nuevo_start_from);  | 
            |
| 128 | 9 | } //First iteration is null  | 
            |
| 129 | |||
| 130 | 9 |             if ($lock_min['num'] !== null && abs(abs($aux_obj_num) - abs($lock_min['num'])) < 1) { | 
            |
| 131 | 9 | $lock_max['num'] = $aux_obj_num;  | 
            |
| 132 | }  | 
            ||
| 133 |             if ($lock_max['num'] !== null && abs(abs($aux_obj_num) - abs($lock_max['num'])) < 1) { | 
            ||
| 134 | 9 | $lock_min['num'] = $aux_obj_num;  | 
            |
| 135 | 9 | }  | 
            |
| 136 | |||
| 137 | 9 | return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier + 1, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, $randomized, $start_from);  | 
            |
| 138 | }  | 
            ||
| 139 | 9 | ||
| 140 | 9 | return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, $randomized, $start_from);  | 
            |
| 141 | }  | 
            ||
| 142 | |||
| 143 | private function lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope): array  | 
            ||
| 144 |     { | 
            ||
| 145 |         if ($aux_obj == $goal) { | 
            ||
| 146 | $lock_min['num'] = $aux_obj_num;  | 
            ||
| 147 | 9 | $lock_min['goal'] = $aux_obj;  | 
            |
| 148 | |||
| 149 | 9 | $lock_max['num'] = $aux_obj_num;  | 
            |
| 150 | $lock_max['goal'] = $aux_obj;  | 
            ||
| 151 | }  | 
            ||
| 152 | |||
| 153 | $going_up = false;  | 
            ||
| 154 |         if ($aux_obj < $goal) { | 
            ||
| 155 | $going_up = true;  | 
            ||
| 156 | }  | 
            ||
| 157 |         if ($aux_obj > $goal) { | 
            ||
| 158 | $going_up = false;  | 
            ||
| 159 | }  | 
            ||
| 160 |         if ($slope == -1) { | 
            ||
| 161 | $going_up = !$going_up;  | 
            ||
| 162 | }  | 
            ||
| 163 | |||
| 164 |         if ($going_up) { | 
            ||
| 165 |             if ($lock_min['num'] !== null && $aux_obj_num < $lock_min['num']) { | 
            ||
| 166 | $lock_max['num'] = $lock_min['num'];  | 
            ||
| 167 | 9 | $lock_max['goal'] = $lock_min['goal'];  | 
            |
| 168 | }  | 
            ||
| 169 | |||
| 170 | 9 | $lock_min['num'] = $aux_obj_num;  | 
            |
| 171 | $lock_min['goal'] = $aux_obj;  | 
            ||
| 172 | 9 | }  | 
            |
| 173 | 8 | ||
| 174 | 8 |         if (!$going_up) { | 
            |
| 175 |             if ($lock_max['num'] !== null && $lock_max['num'] < $aux_obj_num) { | 
            ||
| 176 | 8 | $lock_min['num'] = $lock_max['num'];  | 
            |
| 177 | 8 | $lock_min['goal'] = $lock_max['goal'];  | 
            |
| 178 | }  | 
            ||
| 179 | |||
| 180 | 9 | $lock_max['num'] = $aux_obj_num;  | 
            |
| 181 | 9 | $lock_max['goal'] = $aux_obj;  | 
            |
| 182 | 8 | }  | 
            |
| 183 | return [$lock_min, $lock_max];  | 
            ||
| 184 | 9 | }  | 
            |
| 185 | 9 | ||
| 186 | /**  | 
            ||
| 187 | 9 | * @param $lockMinNum  | 
            |
| 188 | * @param $lockMaxNum  | 
            ||
| 189 | * @param $start_from  | 
            ||
| 190 | * @param $incremental_modifier  | 
            ||
| 191 | 9 | * @return float|int|mixed  | 
            |
| 192 | 8 | */  | 
            |
| 193 | private function getAuxObjNum($lockMinNum, $lockMaxNum, $start_from, $incremental_modifier)  | 
            ||
| 194 |     { | 
            ||
| 195 | $aux_obj_num = null;  | 
            ||
| 196 |         if ($lockMinNum === null && $lockMaxNum === null) { | 
            ||
| 197 | 8 | $aux_obj_num = $start_from;  | 
            |
| 198 | 8 | } //Lower limit found, searching higher limit with * 10  | 
            |
| 199 |         elseif ($lockMinNum !== null && $lockMaxNum === null) { | 
            ||
| 200 |             if ($lockMinNum == $start_from) { | 
            ||
| 201 | 9 | $aux_obj_num = 1;  | 
            |
| 202 | 9 |             } else { | 
            |
| 203 | $aux_obj_num = $lockMinNum * (10 / $incremental_modifier);  | 
            ||
| 204 | }  | 
            ||
| 205 | } //Higher limit found, searching lower limit with * -10  | 
            ||
| 206 |         elseif ($lockMinNum === null && $lockMaxNum !== null) { | 
            ||
| 207 | 9 |             if ($lockMaxNum == $start_from) { | 
            |
| 208 | 9 | $aux_obj_num = -1;  | 
            |
| 209 |             } else { | 
            ||
| 210 | 9 | $aux_obj_num = $lockMaxNum * (10 / $incremental_modifier);  | 
            |
| 211 | }  | 
            ||
| 212 | } //I have both limits, searching between them without decimals  | 
            ||
| 213 |         elseif ($lockMinNum !== null && $lockMaxNum !== null) { | 
            ||
| 214 | $aux_obj_num = round(($lockMinNum + $lockMaxNum) / 2);  | 
            ||
| 215 | }  | 
            ||
| 216 | return $aux_obj_num;  | 
            ||
| 217 | }  | 
            ||
| 218 | }  | 
            ||
| 219 | 
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.