You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.8 KiB

Nesting Models

Objectives

Currently, we only have two unrelated models: people and locations. Let's add the functionality so that whenever we have JSON that represents a person, it will contain a home property, which is the associated location object:

{
    "id":4,
    "name":"Bob",
    "age":25,
    "home": {
        "id":2,
        "street": "123 Fake Street",
        "city": "Aurora",
        "state": "NY"
    }
}

Also, when we have a location, it will contain an array of people objects:

{
    "id":2,
    "street": "123 Fake Street",
    "city": "Aurora",
    "state": "NY",
    "inhabitants":[
        {
            "id":4,
            "name":"Bob",
            "age":25
        },        
        {
            "id":7,
            "name":"Sally",
            "age":74
        },        
    ]
}

This will be a one-to-many relationship

Add home_id to people

First, go into psql and add a home_id INT column to the people table:

ALTER TABLE people ADD COLUMN home_id INT;

Now we can give people homes like so:

UPDATE people SET home_id = 2 WHERE id = 4;

The previous code will find the row with an id of 4 and set the home_id column to 2.

Update People SQL

The first thing we want to do is to update the SQL code in the People class so that each row from the people table is joined onto a corresponding row from the locations table. Once we have the additional columns for each row in the people table, we can use that information to create a home for each Person object. Try the following in psql:

SELECT * FROM people JOIN locations ON people.home_id = locations.id;

The above SQL statement will only show rows where the people have a value in the home_id column. This is because Postgres is attempting to create rows by matching rows in the people table with rows in the locations table, based on whether people.home_id = locations.id. When it encounters a row from the people table where the home_id column is NULL, it can't find any rows from the locations table to match it with, since no rows from locations have an id column of NULL. Since no matches can be found, it doesn't include that row from people. We need to perform the join and then add any missing rows from people:

SELECT * FROM people LEFT JOIN locations ON people.home_id = locations.id;

Now that we have all the people rows, we're ready to plug that into our php. Alter the pg_query code in our People::all() function:

$results = pg_query("SELECT * FROM people LEFT JOIN locations ON people.home_id = locations.id");

If you view http://localhost:8888/people, you'll see something funky. Some people have NULL ids, and other rows have unexpected id columns. The reason for this can be discovered by looking back in psql at our previous LEFT JOIN statement results. You'll notice there are two id columns, one for the people table and one for locations table. When PHP attempts to convert a row into an object, when it reads the people table's id column, creates an id property for the object. It then goes through, adding name, age, and home_id properties. It then reaches the id column for the locations table and overwrites the id property on the $row_object with the value from the id column of the locations table.

To fix this, we can alter our SQL statement to rename one or both of the id columns. Let's rename the id column from the locations table.

SELECT
    people.*,
    locations.id AS location_id,
    locations.street,
    locations.city,   
    locations.state   
FROM people
LEFT JOIN locations
    ON people.home_id = locations.id;

Now if we change our SQL statement in PHP, we'll see better results:

$results = pg_query("SELECT
    people.*,
    locations.id AS location_id,
    locations.street,
    locations.city,
    locations.state
FROM people
LEFT JOIN locations
    ON people.home_id = locations.id;");

Give People Locations

If we put a var_dump inside the while loop in our People::all() function, we can see $row_object now contains information about each person's location (if you're using Postman to view http://localhost:8888/people, you might need to switch to the Raw view).

while($row_object){
    var_dump($row_object); //print $row_object so we can see what it looks like now

    $new_person = new Person(
        $row_object->id,
        $row_object->name,
        $row_object->age
    );
    $people[] = $new_person;

    $row_object = pg_fetch_object($results);
}

Now lets use the additional information on $row_object to create Location objects and add them to $new_person where necessary. First, let's include the Location model at the top of models/person.php.

include_once __DIR__ . '/location.php';

Now, inside the People::all() while loop, we'll add the logic to create a location and add it to $new_person:

$new_person = new Person(
    $row_object->id,
    $row_object->name,
    $row_object->age
);

if($row_object->location_id){ //test if location_id is truthy
    $new_location = new Location( //create a location from the row data
        intval($row_object->location_id), //turn the string into an int
        $row_object->street,
        $row_object->city,
        $row_object->state
    );
    $new_person->home = $new_location; //attach $new_location to $new_person->home
}

$people[] = $new_person;

$row_object = pg_fetch_object($results);

Here we test to see if $row_object->location_id is truthy. If it has a value -- a string representation of the id column of locations table -- then the block of code belonging to the if statement will run, creating $new_location and attaching it to the home property of $new_person.