PHP, Code Challenge

PHP Code Challenge 001

In this PHP code challenge, we learn 2 fundamentals of PHP. How to read a CSV with PHP and how to filter basic model data.
Published: 06/23/2022 9:08 am
Updated: 06/23/2022 9:28 am
#php #code challenge #interview prep #learn php #php test #csv
June 2022 | John B.

What is a PHP Code Challenge?

What is a PHP code challenge? A PHP code challenge is question/task used to test your skills as a PHP developer. Most commonly theses are used in PHP developer interviews, but ultimately can be used at anytime to improve your knowledge and mastery of PHP.

We have created a library of PHP code challenges for you to study from as developers. Employers can also use this library to test potential employees. You can find all the challenges & source code answers in our Github repo.

The Challenge

Read in the file ../assets/trees_planted.csv (which you can find here) and print out the total amount of trees planted per year.

This a fairly basic challenge in which we test 2 fundamentals of PHP: reading a CSV and basic filtering.

Solution Step 1

The first step in any solution is to first clarify and truly understand what is problem asking. For this challenge there are 2 distinct tasks:

  • Read in the file ../assets/trees_planted.csv
  • Print out the total amount of trees planted per year

Solution Step 2

The second step in the solution is to identify any objects that you can code into models. The answer to this question is yes, we have 1 object we should model, the neighborhood plant data. This will model the data found in each row of the csv.

When we look at the top of the CSV file we see the header:

year,month,neighborhood,new trees planted,new flowerbeds planted

Each of these columns should then be a field on our model. Here is what our initial model will look like:

class NeighborhoodPlantData{

    // fields
    public $year;
    public $month;
    public $neighborhood;
    public $new_trees_planted;
    public $new_flowerbeds_planted;

}

Solution Step 3

The third step in the solution is to read each row of the csv.

// open file
$handle = fopen(__DIR__ . "/../assets/trees_planted.csv", "r");
if ($handle) {

    // read each line
    while (($line = fgets($handle)) !== false) {

        // TODO process the line
    }

    // close file
    fclose($handle);
}

Solution Step 4

The fourth step in the solution is to transform each row in the CSV into our defined model. This requires 2 parts. First adjusting our model to convert a csv line into a model. Let's add some constants in the model for the CSV column indexes so that way the values are not hard coded into the constructor.

class NeighborhoodPlantData{

    // indexes
    const CSV_INDEX_YEAR = 0;
    const CSV_INDEX_MONTH = 1;
    const CSV_INDEX_NEIGHBORHOOD = 2;
    const CSV_INDEX_NEW_TREES = 3;
    const CSV_INDEX_NEW_FLOWERBEDS = 4;

    // fields
    public $year;
    public $month;
    public $neighborhood;
    public $new_trees_planted;
    public $new_flowerbeds_planted;

    function __construct($csv_string) {
        // validate has commas
        if(strpos($csv_string, ",") !== false){
            $dataParts = explode(",", $csv_string);

            // set data
            $this->year = (int)$dataParts[self::CSV_INDEX_YEAR];
            $this->month = (int)$dataParts[self::CSV_INDEX_MONTH];
            $this->neighborhood = $dataParts[self::CSV_INDEX_NEIGHBORHOOD];
            $this->new_trees_planted = (int)$dataParts[self::CSV_INDEX_NEW_TREES];
            $this->new_flowerbeds_planted = (int)$dataParts[self::CSV_INDEX_NEW_FLOWERBEDS];
        }
    }
}

The second part is to simply create the model within reading each line from the CSV.

// open file
$handle = fopen(__DIR__ . "/../assets/trees_planted.csv", "r");
if ($handle) {

    // read each line
    while (($line = fgets($handle)) !== false) {

        // process the line
        $neighborhoodPlantData = new NeighborhoodPlantData($line);

    }

    // close file
    fclose($handle);
}

Solution Step 5

The fifth step in the solution is to handle the header row in the CSV. For this we will just validate the year of the data model so let's add a function to our model.


class NeighborhoodPlantData{

    // indexes
    const CSV_INDEX_YEAR = 0;
    const CSV_INDEX_MONTH = 1;
    const CSV_INDEX_NEIGHBORHOOD = 2;
    const CSV_INDEX_NEW_TREES = 3;
    const CSV_INDEX_NEW_FLOWERBEDS = 4;

    // fields
    public $year;
    public $month;
    public $neighborhood;
    public $new_trees_planted;
    public $new_flowerbeds_planted;

    function __construct($csv_string) {
        // validate has commas
        if(strpos($csv_string, ",") !== false){
            $dataParts = explode(",", $csv_string);

            // set data
            $this->year = (int)$dataParts[self::CSV_INDEX_YEAR];
            $this->month = (int)$dataParts[self::CSV_INDEX_MONTH];
            $this->neighborhood = $dataParts[self::CSV_INDEX_NEIGHBORHOOD];
            $this->new_trees_planted = (int)$dataParts[self::CSV_INDEX_NEW_TREES];
            $this->new_flowerbeds_planted = (int)$dataParts[self::CSV_INDEX_NEW_FLOWERBEDS];
        }
    }

    function isValidYear(){
        return (int)$this->year > 0;
    }

}

Now add an if statement while reading the csv.

// all data
$plantData = [];

// open file
$handle = fopen(__DIR__ . "/../assets/trees_planted.csv", "r");
if ($handle) {

    // read each line
    while (($line = fgets($handle)) !== false) {

        // process the line
        $neighborhoodPlantData = new NeighborhoodPlantData($line);

        // if valid year, then add to array
        if($neighborhoodPlantData->isValidYear()){
        }

    }

    // close file
    fclose($handle);
}

Solution Step 6

The sixth step in the solution is to sum trees planted per year. If there is a valid year, add the trees planted count to totals array.

// all data
$plantData = [];

// open file
$handle = fopen(__DIR__ . "/../assets/trees_planted.csv", "r");
if ($handle) {

    // read each line
    while (($line = fgets($handle)) !== false) {

        // process the line
        $neighborhoodPlantData = new NeighborhoodPlantData($line);

        // if valid year, then add to array
        if($neighborhoodPlantData->isValidYear()){
            if(!isset($plantData[$neighborhoodPlantData->neighborhood])){
                $plantData[$neighborhoodPlantData->neighborhood] = 0;
            }
            $plantData[$neighborhoodPlantData->neighborhood] += $neighborhoodPlantData->new_trees_planted;
        }

    }

    // close file
    fclose($handle);
}

Final Solution Step

The final step in the solution is to print the planted sums per year. We will simple iterate through the sum array and echo the result. Let's also do just a little bit of extra credit and sort the array by neighborhood.

// sort neighborhoods
ksort($plantData);

// print out results
foreach($plantData AS $neighborhood => $tree_planted_count){
    echo $neighborhood . ": " . $tree_planted_count . PHP_EOL;
}

Recap

Now we have a working solution to the challenge. The most important thing is that it actually accomplishes what the challenged asked for. We also were able to add a little extra to make it a better solution & reusable for the future. Here are the key aspects:

  • Built a model for the data in the CSV
  • Used a custom constructor in the model
  • Used a custom function in the model
  • Used constants in the model opposed to hard coded values
  • Read the CSV
  • Sorted array of results
  • Printed results

You can find the source code to this challenge and all the other challenges in our Github repo.