Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Eddie D's avatar

Undefined variable, but variable is initialized?

I'm getting an undefined variable error, but when I look at the code I can see at the beginning of the function the variable is initialized as an empty string. Please note this code has been running for several months(it's a command that is scheduled to run every night), and this undefined variable error just started happening in the last 2 days but the code has not changed... Any recommendations or pointers would be greatly appreciated.

Undefined variable: discrepTable
/home/forge/default/releases/20220523175749/app/Console/Commands/Epicor/ImportEpicorPricing.php :190

Here is the relevant code:

                        $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td>" . $qty . "</td>" .
                                        "<td>" . $beforePartPrice->price . "</td>" .
                                        "<td>" . $partPrice->price . "</td>" .
                                        "<td>Updated Pricing</td>" .
                                    "</tr>";

At the top of the function $discrepTable variable is initialized as an empty string:

    public function handle()
    {
        $discrepTable = "";
        $errors = [];
        $differencesFound = false;

        Log::channel('epicorLog')->info($this->signature . ": " . $this->description);

        $columns = ['part_name', 'quantity', 'price_before', 'price_after', 'comments'];

        $discrepTable .= "<table border=1><tr>";
0 likes
13 replies
swim360's avatar

$discrepTable .= "" . "" . $part->name . "" . "" . $qty . "" . "" . $beforePartPrice->price . "" . "" . $partPrice->price . "" . "Updated Pricing" . "" ;

I think here you need to wrap all html in quotation mark. Like you did here

$discrepTable .= " ";

1 like
vincent15000's avatar

That's not possible that the code worked and without any change doesn't work any more.

There is necessarily some change : if it's not around the code, it could be around the server configuration, the datas, ... ?

Eddie D's avatar

Here is the full function. I double checked Git commit history, and this code has not changed since May. It runs every night which I know as it emails me... If helpful it's comparing information on our webstore to information in our ERP system to identify any possible discrepancies between the two, and sends an email notifying us of any discrepencies...

    public function handle()
    {
        $discrepTable = "";
        $errors = [];
        $differencesFound = false;

        Log::channel('epicorLog')->info($this->signature . ": " . $this->description);

        $columns = ['part_name', 'quantity', 'price_before', 'price_after', 'comments'];

        $discrepTable .= "<table border=1><tr>";
        foreach($columns as $column) $discrepTable .= "<th>$column</th>";
        $discrepTable .= "</tr>";


        //TODO this harcodes a pricelist, make the BAQ smarter to chose the correct price
        //price list based on expiration date
        $ePriceList = EpicorRepository::getPriceList();

        $parts = Part::notOrphaned()
            ->active()
            //->notIgnored()
            ->associatedWithSellableProduct()
            ->get();

        //$parts = Part::where('name', 'like', 'TS-7800-V2-DMN1I')->get();

        $partsUpdated = collect();
        $partsWithIssues = collect();

        foreach($parts as $part) {

            if ($ePriceList->contains('name', $part->name)) {

		        //Warning - the BAQ does not filter out any expired prices nor does this code
                $epicorPartPricing = $ePriceList->filter(function ($value, $key) use ($part) {
                    return ($value->name == $part->name) &&
                        ($value->quantity <= 100) &&
                        //TODO make the BAQ smarter so we don't have to do this type of a check
			            ($value->listCode == "Master3"); 
                });

                if($epicorPartPricing->count() == 0) {

                    $differencesFound = true;

                    $msg = "Part $part->name didn't have any pricing in Epicor.";
                    Log::channel('epicorLog')->error("$msg Cowardly skipping...");
                    $errors[] = $msg;

                    $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td></td>" .
                                        "<td></td>" .
                                        "<td></td>" .
                                        "<td>$msg</td>" .
                                    "</tr>";
                    continue;
                }

                $partPrices = collect();
                $quantities = [];

                // Gather all the price breaks to be able to determine max_quantity
                foreach($epicorPartPricing as $epicorPartPrice) {
                    $quantities[] = $epicorPartPrice->PLPartBrk_Quantity;
                }

                foreach($epicorPartPricing as $epicorPartPrice) {
                    $min_quantity = $epicorPartPrice->PLPartBrk_Quantity;
                    $max_quantity = 99999;

                    $qty_array_index = array_search($min_quantity, $quantities);

                    if (isset($quantities[$qty_array_index + 1])) {
                        $max_quantity = $quantities[$qty_array_index + 1] - 1;
                    }

                    $partPrices->push(new PartPrice([
                        'min_quantity' => (integer)$min_quantity,
                        'max_quantity' => (integer)$max_quantity,
                        'price' => (double)$epicorPartPrice->PLPartBrk_UnitPrice,
                        'part_id' => $part->id,
                        'is_unavailable' => (boolean)$epicorPartPrice->PLPartBrk_callUs_c,
                    ]));
                }


                $clonePrices = clone $part->prices;

                try {
                    \DB::beginTransaction();
                    $part->prices()->delete();
                    $part->prices()->saveMany($partPrices);
                    \DB::commit();
                }
                catch(Throwable $e) {

                    $msg = "$part->name threw error:\n$e";
                    Log::channel('epicorLog')->error("  Error! Rolling back changes because:\n$e");
                    $errors[] = $msg;

                    $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td></td>" .
                                        "<td></td>" .
                                        "<td></td>" .
                                        "<td>$msg</td>" .
                                    "</tr>";
                    \DB::rollback();
                    continue;
                }

                $part->refresh();

                // Compare after with before to get any new or updated pricing.
                $part->prices->each(function($partPrice, $index) use ($part, $clonePrices) {

                    $qty = $partPrice->min_quantity;
                    $beforePartPrice = $clonePrices->where('min_quantity', $qty)->first();

                    if(!$beforePartPrice) {
                        $differencesFound = 1;

                        Log::channel('epicorLog')->info("For qty $qty, before: null, after: $partPrice->price");
                        $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td>" . $qty . "</td>" .
                                        "<td></td>" .
                                        "<td>" . $partPrice->price . "</td>" .
                                        "<td>Pricing for this quantity was added</td>" .
                                    "</tr>";
                    }
                    else if ($beforePartPrice->price != $partPrice->price) {
                        $differencesFound = true;

                        Log::channel('epicorLog')->info("For qty $qty, before: $beforePartPrice->price, after: $partPrice->price");
                        $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td>" . $qty . "</td>" .
                                        "<td>" . $beforePartPrice->price . "</td>" .
                                        "<td>" . $partPrice->price . "</td>" .
                                        "<td>Updated Pricing</td>" .
                                    "</tr>";
                    }

                    if($beforePartPrice && ((boolean)$beforePartPrice->is_unavailable !== (boolean)$partPrice->is_unavailable)) {
                        $differencesFound = true;

                        $beforeStr = (boolean)$beforePartPrice->is_unavailable ? "Yes" : "No";
                        $afterStr = (boolean)$partPrice->is_unavailable ? "Yes" : "No";

                        Log::channel('epicorLog')->info("For qty $qty, is_unavailable before: $beforeStr, after: $afterStr");
                        $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td>" . $qty . "</td>" .
                                        "<td> </td>" .
                                        "<td></td>" .
                                        "<td>Changed 'Call Us' state from '$beforeStr' to '$afterStr'</td>" .
                                    "</tr>";
                    }

                });

                // Quick compare before with after to see if any removed pricing
                $clonePrices->each(function($clonePartPrice, $index) use ($part) {
                    $qty = $clonePartPrice->min_quantity;
                    $afterPartPrice = $part->prices->where('min_quantity', $qty)->first();

                    if(!$afterPartPrice) {
                        Log::channel('epicorLog')->info("For qty $qty, before: $clonePartPrice->price, after: null");
                        $discrepTable .= "<tr>" .
                                        "<td>" . $part->name . "</td>" .
                                        "<td>" . $qty . "</td>" .
                                        "<td>" . $clonePartPrice->price . "</td>" .
                                        "<td></td>" .
                                        "<td>Pricing for this quantity was removed</td>" .
                                    "</tr>";
                    }
                });

            }
            else {
                Log::channel('epicorLog')->info("...Skipping no pricelist information.");

            }

        }

        Cache::tags('pricing')->flush();
        Log::channel('epicorLog')->info("Cleared pricing cache for products.");


        // If there's more than one line in the file (header), then we want to report it
        if($differencesFound) {

            $msg = new GenericMessage();

            $to = ['[email protected]'];
            $msg->subject = 'Part Pricing Updated on Web Based on Epicor Data: ' . count($errors) . " errors detected";

            $msg->content = "<h1>Errors</h1>";
            foreach($errors as $error) $msg->content .= "<li>$error</li>";
            $msg->content .= "</ul><br />";

            $msg->content .= "<h1>Error Details</h1>";
            $msg->content .= $discrepTable;

            if(\App::environment('local')) {
                $to = [env('DEVELOPER_EMAIL')];
            }

            Log::channel('epicorLog')->info("Part pricing updated and sent via email to " . implode(', ', $to) . " for review.");

            Mail::to($to)->queue($msg);
        } 
        else {

            Log::channel('epicorLog')->info("No part pricing updates detected, so not sending an email.");
        }
    }
Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@Eddie D think here. But your code is hard to read. Can I suggest blade for html

$part->prices->each(function($partPrice, $index) use ($part, $clonePrices, &$discrepTable ) {
Eddie D's avatar

@Sinnbeck Great eye thank you sir for not only spotting the issue but also suggesting how to correct it(inheriting variables from parent scope)!

Sinnbeck's avatar

@Eddie D happy to help. Be aware of the & which tells php to allow you to change the value in the function

1 like
Sinnbeck's avatar

@vincent15000 my guess would be to deploying broken code and not restarting php-fpm and old code keeps working

1 like
kokoshneta's avatar

@vincent15000 My guess would be the server was upgraded from PHP 7.4 to 8.0 or 8.1.

Prior to 8.0, the default error reporting level was E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED, and attempting to read an undefined variable resulted in a notice (so Laravel would just ignore it). Since 8.0, the default error reporting level is E_ALL, and attempting to read an undefined variable results in a warning (which Laravel converts to an exception).

The Laravel default has always been E_ALL, but this may be overridden to the PHP default in the app.

2 likes

Please or to participate in this conversation.