Lesson Weekend

Find a Task by ID

Now we want to be able to find a single task from our database.

When we insert a record into our database, it is assigned a unique ID, which we can use to find the record later. These unique IDs are important, because no two records will have the same ID. In order to associate an ID with our task object we'll have to add it to our class. Let's start with our tests, then we'll work through developing the functionality, and afterwards talk through how the tests work. Here's our tests for getId() and find(id):

tests/TaskTest.php
function testGetId()
{
    //Arrange
    $description = "Wash the dog";
    $test_task = new Task($description);
    $test_task->save();

    //Act
    $result = $test_task->getId();

    //Assert
    $this->assertTrue(is_numeric($result));
}

function testFind()
{
    //Arrange
    $description = "Wash the dog";
    $description_2 = "Water the lawn";
    $test_task = new Task($description);
    $test_task->save();
    $test_task_2 = new Task($description_2);
    $test_task_2->save();

    //Act
    $id = $test_task->getId();
    $result = Task::find($id);

    //Assert
    $this->assertEquals($test_task, $result);
}

Now let's break down the problem. There are a few things we need to do before we can search for a task by ID:

1. Save New Task

For a brand new task, we need to be able to create an object in memory without an ID. We'll do this with a default value as we did with our CD class.

src/Task.php
<?php
    class Task
    {
        private $description;
        private $id;

        function __construct($description, $id = null)
        {
            $this->description = $description;
            $this->id = $id;
        }
...
        function getId()
        {
            return $this->id;
        }
    }

In our Task class we needed to add in $id as a variable, as well as add it to the construct method. The default value, null tells the constructor what to assign if no argument is given.

2. Save and Set ID

For a task object ONLY in local memory, we need to set the ID on the object after it is saved to the database. Now that we have a task instantiated in local memory, we can save the object and set the corresponding unique SQL ID after success. Here's the code to achieve this:

src/Task.php
function save()
{
      $executed = $GLOBALS['DB']->exec("INSERT INTO tasks (description) VALUES ('{$this->getDescription()}');");
      if ($executed) {
         $this->id = $GLOBALS['DB']->lastInsertId();
         return true;
      } else {
         return false;
      }
}

We've only added one line to our save() method here. When we successfully insert the information we'll use the PDO method, lastInsertId(), to get the SQL id for our previously saved task.

3. Get Task from Database and Instantiate With ID

For a task record in the database, we need to be able to create an object in memory that includes that task's ID. This functionality is actually already completed by virtue of ID's default value in the Task constructor.

Notice the two different ways we use our constructor now. When we instantiate we use our constructor with one argument like this:

$description = "Wash the dog";
$test_task = new Task($description);

Let's write our find() and getAll() now to reflect our changes. In both cases our constructors will take two arguments. The second argument is the ID that the database has assigned to the row upon saving:

static function getAll()
{
    $returned_tasks = $GLOBALS['DB']->query("SELECT * FROM tasks;");
    $tasks = array();
    foreach($returned_tasks as $task) {
      $description = $task['description'];
      $id = $task['id'];
      $new_task = new Task($description, $id);
      array_push($tasks, $new_task);
    }
    return $tasks;
}
...

static function find($search_id)
{
    $returned_tasks = $GLOBALS['DB']->prepare("SELECT * FROM tasks WHERE id = :id");
    $returned_tasks->bindParam(':id', $search_id, PDO::PARAM_STR);
    $returned_tasks->execute();
    foreach ($returned_tasks as $task) {
       $task_description = $task['description'];
       $task_id = $task['id'];
       if ($task_id == $search_id) {
          $found_task = new Task($task_description, $task_id);
       }
    }

    return $found_task;
}

You may noticed that we have not built a setter for our $id variable. When working with auto-incrementing values in a database, we rely on SQL to assign the id in sequential order. We don't want this value to be set in any way except saved to the database, or when an object is initialized from a database record.

Tests Explained

Now let's look at how we've achieved passing tests.

tests/TaskTest.php
function test_getId()
{
    //Arrange
    $description = "Wash the dog";
    $test_Task = new Task($description);
    $test_Task->save();

    //Act
    $result = $test_Task->getId();

    //Assert
    $this->assertTrue(is_numeric($result));
}

function testFind()
{
    //Arrange
    $description = "Wash the dog";
    $description_2 = "Water the lawn";
    $test_task = new Task($description);
    $test_task->save();
    $test_task_2 = new Task($description_2);
    $test_task_2->save();

    //Act
    $id = $test_task->getId();
    $result = Task::find($id);

    //Assert
    $this->assertEquals($test_task, $result);
}

Our new test_getId() is instantiating a Task object in local memory without an ID. In this case the default value of test_Task->getId() is 'null'. Then we save the ID. All we need to do is test, after saving our test_task, that the return value has become a numerical value rather than 'null'.

Our second test test is pretty straightforward. We instantiate two Tasks, then search the database with the ID from one to make sure the returned object corresponds with the object in local memory with the same ID.