Lesson Monday

Now, let's get the tasks and categories talking to each other. In my new version of this program, I'm going to require that a task belong to a category, and be initialized with the category ID as category_id. Here is how we need to change our tests for the Task class to reflect this change:

TaskTest.php
<?php

    /**
    * @backupGlobals disabled
    * @backupStaticAttributes disabled
    */

    require_once "src/Task.php";
    require_once "src/Category.php";

    $server = 'mysql:host=localhost:8889;dbname=to_do_test';
    $username = 'root';
    $password = 'root';
    $DB = new PDO($server, $username, $password);




    class TaskTest extends PHPUnit_Framework_TestCase
    {

        protected function tearDown()
        {
            Task::deleteAll();
            Category::deleteAll();
        }

        function testGetId()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();

            $description = "Wash the dog";
            $category_id = $test_category->getId();
            $test_task = new Task($description, $category_id);
            $test_task->save();

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

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

        function testGetCategoryId()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();

            $category_id = $test_category->getId();
            $description = "Wash the dog";
            $test_task = new Task($description, $category_id);
            $test_task->save();

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

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

        function testSave()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();

            $description = "Wash the dog";
            $category_id = $test_category->getId();
            $test_task = new Task($description, $category_id);

            //Act
            $executed = $test_task->save();

            //Assert
            $this->assertTrue($executed, "Task not successfully saved to database");
        }

        function testGetAll()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();
            $category_id = $test_category->getId();

            $description = "Wash the dog";
            $test_task = new Task($description, $category_id);
            $test_task->save();

            $description_2 = "Water the lawn";
            $test_task_2 = new Task($description_2, $category_id);
            $test_task_2->save();

            //Act
            $result = Task::getAll();

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

        function testDeleteAll()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();
            $category_id = $test_category->getId();

            $description = "Wash the dog";
            $test_task = new Task($description, $category_id);
            $test_task->save();

            $description2 = "Water the lawn";
            $test_task2 = new Task($description2, $category_id);
            $test_task2->save();

            //Act
            Task::deleteAll();

            //Assert
            $result = Task::getAll();
            $this->assertEquals([], $result);
        }

        function testFind()
        {
            //Arrange
            $name = "Home stuff";
            $test_category = new Category($name);
            $test_category->save();
            $category_id = $test_category->getId();

            $description = "Wash the dog";
            $test_task = new Task($description, $category_id);
            $test_task->save();

            $description_2 = "Water the lawn";
            $test_task_2 = new Task($description_2, $category_id);
            $test_task_2->save();

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

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

    }
?>

Here we have added the creation of new Category objects to each test. We've also added $category_id into our creation of new tasks and we have required Category.php at the top of our file. Notice that we are now also calling both Task::deleteAll(); and Category::deleteAll(); during our teardown function. Since we are using both categories and tasks tables at the same time in our tests we will need to clear them both each time. Before we work on our model, we need to add a column in our tasks table to store the category_id:

> USE to_do;
> ALTER TABLE tasks ADD category_id int;
> DROP DATABASE to_do_test;

Then create another copy called to_do_test in phpMyAdmin.

Now, here's the updated Task class:

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

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

        function setDescription($new_description)
        {
            $this->description = (string) $new_description;
        }

        function getDescription()
        {
            return $this->description;
        }

        function getId()
        {
            return $this->id;
        }

        function getCategoryId()
        {
            return $this->category_id;
        }

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

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

        static function deleteAll()
        {
           $GLOBALS['DB']->exec("DELETE FROM 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'];
                $category_id = $task['category_id'];
                $task_id = $task['id'];
                if ($task_id == $search_id) {
                   $found_task = new Task($task_description, $category_id, $task_id);
                }
            }

            return $found_task;
        }
    }
?>

We have added category_id into all of our methods, but note that in the save method, we didn't include ' ' around the {} string interpolation of $category_id because we want the category_id property to go into the database as an integer.

Now that the relationship is set up, we can write a method to get all of the tasks associated with a particular list. Let's call it getTasks() and we will be adding this method to the Category class. Here are the specs for it:

CategoryTest.php
function testGetTasks()
{
    //Arrange
    $name = "Work stuff";
    $test_category = new Category($name);
    $test_category->save();

    $test_category_id = $test_category->getId();

    $description = "Email client";
    $test_task = new Task($description, $test_category_id);
    $test_task->save();

    $description_2 = "Meet with boss";
    $test_task_2 = new Task($description_2, $test_category_id);
    $test_task_2->save();

    //Act
    $result = $test_category->getTasks();

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

Because we are creating new Task objects within the Category class, we need to add require_once "src/Task.php"; to the top of our CategoryTest.php file. In this test, we are creating a new instance of Category and then creating two new instances of Task with the category_id property set to the ID of the new instance of Category. Let's also update the teardown function here to call both deleteAll() methods. Don't forget to add in your require_once "src/Task.php"; line to the top of your CategoryTest.php file too so that it knows what a Task means.

CategoryTest.php
        protected function tearDown()
        {
            Category::deleteAll();
            Task::deleteAll();
        }

Let's see the code to make this pass:

src/Category.php
function getTasks()
{
    $tasks = Array();
    $returned_tasks = $GLOBALS['DB']->query("SELECT * FROM tasks WHERE category_id = {$this->getId()};");
    foreach($returned_tasks as $task) {
        $description = $task['description'];
        $task_id = $task['id'];
        $category_id = $task['category_id'];
        $new_task = new Task($description, $category_id, $task_id);
        array_push($tasks, $new_task);
    }
    return $tasks;
}

In this method, we are gathering all of the tasks from the database where the category_id of that task is the ID of the category we are calling the method on. Then we loop through each task, pull out the correct property values and then create a new instance of Task with those properties. We return the $tasks array after pushing that new object into it.