Global variables declared outside the expected solution functions are not recognized?

Hello, I am working on a PHP exercise: Resistor Color in PHP on Exercism

I am not entirely sure why the solution I am attempting is not working.

In the following code, I have declared the $colorCodes variable above the functions, and then used the global keyword to import the global $colorCodes I have declared.

If I var_dump($colorCodes); right after the declaration of the variable, it shows me the populated array, however if I do the same inside the getAllColors() function, I am getting NULL.

declare(strict_types=1);

$colorCodes = array(
    'Black' => 0,
    'Brown' => 1,
    'Red' => 2,
    'Orange' => 3,
    'Yellow' => 4,
    'Green' => 5,
    'Blue' => 6,
    'Violet' => 7,
    'Grey' => 8,
    'White' => 9,
);

function getAllColors(): array
{
    global $colorCodes;
    return array_keys($colorCodes);
}

function colorCode(string $color): int
{
    global $colorCodes;
    return $colorCodes[$color];
}

When I run ./phpunit ResistorColorTest.php I get

1) ResistorColorTest::testColors
TypeError: array_keys(): Argument #1 ($array) must be of type array, null given

I cannot see why this is not working when it seems pretty much identical to some of the examples in the PHP Manual: PHP: Variable scope - Manual

Any insight or help is appreciated.

I can’t explain it.

You might look into constants instead.

@knightofrohan You did understand the docs correctly. But the way the test includes your code makes your variables outside the functions local to a method:

class ResistorColorTest extends PHPUnit\Framework\TestCase
{
    public static function setUpBeforeClass(): void
    {
        require_once 'ResistorColor.php';
    }
[...]

You can a) return the array from a function or b) use constants like @glennj suggested. Both ways lead to breaking out of the local scope of the method and add your symbol to the global scope.

By the way: this is a quirk that catches even very experienced PHP programmers. It’s general advice, and also easier to remember: Do not use global variables.

1 Like

Thank you! I did end up just using another method to solve the problem but I wanted to understand why it wasn’t working just to learn.

To that end, just so I understand, if I may ask some more questions about it, I think this is a good opportunity for me to understand how require_once or require works. The docs ultimately tell me to refer to the include docs: PHP: include - Manual

Basically, from reading it over, it looks like the PHP code in the included file is “executed” which would mean that the $colorCodes variable is declared, but since the getAllColors() function is not called in that file, the code inside the function is not exactly executed, PHP just knows the function of that name is declared, so the global $colorCodes; line is not executed and we end up with the $colorCodes variable not being global.

So at this point, PHP just knows the variable declaration in that file, that is not global, and the function declarations. Does that sound right?

So I thought I would move the global statement outside the functions entirely. Oddly, moving the global statement outside the function and after the $colorCodes array declaration, does not seem to work, but moving it before the declaration does.

I still had to add more global statements inside the functions to make it work but eventually it did work, like this:

declare(strict_types=1);

global $colorCodes;

$colorCodes = array(
    'black' => 0,
    'brown' => 1,
    'red' => 2,
    'orange' => 3,
    'yellow' => 4,
    'green' => 5,
    'blue' => 6,
    'violet' => 7,
    'grey' => 8,
    'white' => 9,
);

function getAllColors(): array
{
    global $colorCodes;
    return array_keys($colorCodes);
}

function colorCode(string $color): int
{
    global $colorCodes;
    return $colorCodes[$color];
}

Is this an intricacy worth understanding? Any idea why it happens?

Lastly, I will generally stay away from global. Just thought I’d try diving in now that I had encountered this issue. Thank you @mk-mxp and @glennj for your suggestions.

Well, it is not “the execution” that makes it fail. It is something you will find sometimes in day to day programming, and as you are asking…

First of all, PHP does not directly execute code. It parses it, then compiles it to opcodes, then executes it. Functions are always global and the parser notes their names until the end of all parsing and when compilation begins, all the functions are known and may be used anywhere. And later may be executed depending on the execution path. But that has nothing to do with your question…

There are some special rules that apply to files being included / required, like namespaces are not applied from the parent scope, Magic Constants are set again, but the scope is inherited. So all statements are executed in that scope.

But global is not a statement that is executed. It tells PHP to treat the name of the variable as in global scope from here on. If you put global after writing to the variable, the value is first written into a local variable and then you tell PHP to use a global variable of the same name from now on (which does not exist).

This means, it only works in the correct sequence. First tell PHP to connect the name with the variable from global scope, then write to that variable. That way PHP knows you want to write to the global scope and not to a new local variable.

1 Like

Thank you for taking the time to respond. That’s really interesting.

The reason why I didn’t consider that the global statement was a declaration of a new variable was because of the examples in the manual in the code below. Here it seems like the global statement inside the function is successfully making a reference to a variable that was previously declared and assigned a value. I don’t think I understand that.


<?php
$a = 1;
$b = 2;

function Sum()
{
    global $a, $b;

    $b = $a + $b;
} 

Sum();
echo $b;
?>

Is there a part of the manual that explains how the parsing and compilation happens?

I’ll try to make it clear by adding comments to the code and leaving away all that does not help with the global thing.

// Create a global variable `global $a` by writing `1` to it.
// PHP does not have "variable declarations", they are created
// by writing to them.
$a = 1;

// Enter local scope `Sum`
function Sum()
{
    // Create a local variable `Sum $a` by writing `2` to it.
    $a = 2;

    // Read and write to local variable `Sum $a`.
    $a = $a+7; // => 9

    // Tell PHP, that when I say `$a` here after
    // in scope `Sum`, I mean `global $a`.
    global $a;

    // Read and write to global variable `global $a`.
    $a = $a+5; // => First call 6, second call 11

    // Pass back value of global variable `global $a`.
    return $a; // => First call 6, second call 11

// Leave local scope `Sum`
}

echo Sum(); // => 6

// Read global variable `global $a`.
echo $a; // => 6

echo Sum(); // => 11

I do not know of a direct manual entry about parsing, compiling and executing of PHP. There is scattered hints like “Any valid PHP code may appear inside a function, even other functions and class definitions.”, which only can be done when first parsing all code and then executing it later. Or the documentation of opcache, which helps speed up the parse, compile, execute sequence by caching the compiled files.

1 Like

And to make sure you can see the difference to the Exercism exercise, here is code within the test method, that shows how this behaved when you used the wrong order:

// Enter local scope of class ResistorColorTest
class ResistorColorTest extends PHPUnit\Framework\TestCase
{
    // Enter local scope of method setUpBeforeClass
    public static function setUpBeforeClass(): void
    {
        // Instead of this, I will inline the code to better show
        // what's happening
        // require_once 'ResistorColor.php';

        // Create a local variable
        // `setUpBeforeClass $colorCodes` by writing `1` to it.
        $colorCodes = 1;

        // This is what you did in your first try to add `global`
        // "Oddly, moving the global statement outside the
        // function and after the $colorCodes array declaration[...]"
        // Tell PHP, that when I say `$colorCodes` here after
        // in scope `setUpBeforeClass`, I mean `global $colorCodes`.
        global $colorCodes;

        // Enter local scope `getAllColors`
        function getAllColors()
        {
            // Tell PHP, that when I say `$colorCodes` here after
            // in scope `getAllColors`, I mean `global $colorCodes`.
            global $colorCodes;

            // Pass back value of global variable
            // `global $colorCodes` (which does not exist).
            return $colorCodes; // => PHP Warning: Read before write

        // Leave local scope `getAllColors`
        }
    // Leave local scope of method setUpBeforeClass
    }
[...]
// Leave local scope of class ResistorColorTest
}
1 Like