Search code examples
phparrayssplitscandir

Strange PHP Scandir or Split error with array


First, please forgive me, I'm still very new to this and I'm a little tired right now.

I'm at my wits end over this. I've written a little PHP code so that a site I'm working on can update it's newsletters quickly and easily. The goal is to show the first newsletter as an embedded pdf and then show all the rest as links below it, so all the person who writes the newsletters needs to do is place them in the folder and use the proper naming format.

I'm using scandir to scan the directory and return all the files in descending order, which will give the most recent file first provided they are named correctly (YYYY/MM). It then splits that filename up and passes the month into a function that returns the actual name of the month, and plugs all of that into the html and generates the page.

It works perfectly, except for one thing. One file won't show the month. You can see in the screenshot that the file from August 2014 won't show the month, and I simply cannot figure it out. (Sorry about the stuff on the bottom, I can't post more than two links because I don't have enough rep so I had to make all my images into one, disregard the bottom half)

Here's the code for my main program (sorry about the formatting, I had to combine things because of these stupid restrictions)

//sort in descending order so the most recent date is first
$newsletterarray=scandir("../docs/newsletter", SCANDIR_SORT_DESCENDING);

//print_r($newsletterarray); //shows all the values             

//Splits the selected newsletter array into another array, [1] will be the month which can be passed into month.php
$month = split("_", $newsletterarray[0]);

$filenameSplit = split("_", $newsletterarray[0]);
//The most recent newsletter is embedded
echo('<p>
    <a href="../docs/newsletter/' . $newsletterarray[0] . '"><h4 align="justify">' . getMonth($filenameSplit[1]) . ' ' . $filenameSplit[0] . ' Newsletter</h4></a>
</p>                            
<p>
    <object width="50%" height="500px" data="../docs/newsletter/' . $newsletterarray[0] . '" type="application/pdf"></object>
</p><br> <h4>Older Newsletters</h4>');

//starts at 1 so it skips the most recent newsletter
//increments until it reaches 3 less than max, otherwise it shows directory stuff   
for( $i = 1; $i <= (count($newsletterarray) - 3); $i++) {
    $filenameSplit = split("_", $newsletterarray[$i]);  
    echo('<a href="../docs/newsletter/' . $newsletterarray[$i] . '">' . getMonth($filenameSplit[1]) . ' ' . $filenameSplit[0] . ' Newsletter</a><br>');
} 

function getMonth($nummonth) {
    switch ($nummonth) {
        case 01:
            return "January";
            break;
        case 02;
            return "February";
            break;
        case 03:
            return "March";
            break;
        case 04:
            return "April";
            break;
        case 05:
            return "May";
            break;
        case 06:
            return "June";
            break;
        case 07:
            return "July";
            break;
        case 08:
            return "August";
            break;
        case 09:
            return "September";
            break;
        case 10:
            return "October";
            break;
        case 11:
            return "November";
            break;
        case 12:
            return "December";
            break;
        default:
            return "";
            break;
    }
}

In the screenshot you can see some of the troubleshooting I did. You can see that the split says that 1 should be the correct value for 08, but when I pass it into the getMonth function it doesn't work. You'll also see that passing in 08 works just fine, so the error doesn't seem to be in the getMonth function. In addition to this, I tried adding a file to the actual directory, which did not change the error at all, but the intended functionality did work. I also tried removing the problematic file and renaming another file to have the same name, but the same error occurred.

I'm really hoping this is just a stupid error where I wrote something that's preventing this specific file from working, it's really causing me some grief. Thank you.


Solution

  • Your biggest problem is mixing of types. Your split() calls will return an array of strings for the month ('01', '02', etc.), but your getMonth() function is defined so that it switches on values as integers (01, 02, etc.).

    Now, PHP will permit this because it will do some type juggling for you. But you're running into further trouble with how the numbers inside your switch are being interpreted. Specifically, your leading zeros are causing them to be interpreted as octal (base-8) numbers rather than the decimal (base-10) numbers that you're expected.

    We could delve into the gory details, but they're really not important here because you should fix this by representing your numbers consistently. The quickest way to do this is to replace each of the switch values inside getMonth() with quoted values:

    function getMonth($nummonth) {
        switch ($nummonth) {
            case '01':
                return "January";
                break;
            case 02;
                return "February";
                break;
            case '03':
                return "March";
                break;
            case '04':
                return "April";
                break;
            case '05':
                return "May";
                break;
            case '06':
                return "June";
                break;
            case '07':
                return "July";
                break;
            case '08':
                return "August";
                break;
            case '09':
                return "September";
                break;
            case '10':
                return "October";
                break;
            case '11':
                return "November";
                break;
            case '12':
                return "December";
                break;
            default:
                return "";
                break;
        }
    }
    

    That should get things working for you. It works by looking at those values as strings, which will make them match the values you're passing in for $nummonth without any type juggling to breaking things.

    Beyond that, though, allow me a few other tips:

    1. The split() function is dead, not even available in recent PHP versions. If you need to split a string by regular expression, use preg_split(). In this case, you are just splitting by a string constant ('_'), so you can use explode(), which does exactly what you need here. Simply switch your split() calls to explode() calls, without changing the parameters.
    2. When you're using echo to spit out the HTML, you should really be escaping it. See PHP's htmlentities() function. With the specific filename data you've shown, you won't run into a problem, but other data could cause the page to misbehave or present a security vulnerability.
    3. The $month variable line can be removed. You don't use that variable, and right after it, you have the $filenameSplit variable that is the same thing but much better named.
    4. The getMonth() function itself could be simplified. For a simple mapping like this, I often like to use an array. With this technique, your function might look like getMonth2(), below. See PHP's docs on arrays to learn how this works. However, in this specific case of converting a month number to a name, you can reduce it to a one-liner like in getMonth3(), below. See the docs on date() and strtotime() to learn about this approach.

    Alternative 1: using an array as a map:

    function getMonth2($nummonth) {
        $months = array(
            '01' => 'January',
            '02' => 'February',
            '03' => 'March',
            '04' => 'April',
            '05' => 'May',
            '06' => 'June',
            '07' => 'July',
            '08' => 'August',
            '09' => 'September',
            '10' => 'October',
            '11' => 'November',
            '12' => 'December',
        ); //$months
    
        return $months[$nummonth];
    } //getMonth2
    

    Alternative 2: using PHP's date-related functions

    function getMonth3($nummonth) {
        return date('F', strtotime("2017-$nummonth"));
    } //getMonth3