Lesson Monday

Lesson goals

  • Learn to create objects in Silex

Let's learn how to use the objects we've been practicing with inside of a Silex app by going back to our rectangle website. First, we will create a project folder just like before, including the app and web folders. In the top level of the project folder create a composer.json file with the same text as last time in it.

composer.json
{
    "require": {
        "silex/silex": "~1.1"
    }
}

Then run the $ composer install command in the top level of the project folder to create the vendor folder with all of the Silex files in it.

Now, create index.php with the same text as last lesson in it, and put it inside of the web folder. Start your server in this folder while you're here too.

web/index.php
<?php
    $website = require_once __DIR__.'/../app/app.php';
    $website->run();
?>

Next, create app.php inside of the app folder to hold our routes.

NOTE: If you should encounter timezone errors, please include this code at the top of your app.php, but inside your <?php opening tag:

date_default_timezone_set('America/Los_Angeles');

app/app.php
<?php
    require_once __DIR__."/../vendor/autoload.php";

    $app = new Silex\Application();

    $app->get("/", function() {
        return "Home";
    });

    return $app;
?>

We use the keyword require_once to load the Silex files that are inside of vendor. Then we create a new $app object using the Silex\Application class. We create a route for the root path which just returns the placeholder text "Home" right now, and lastly we return the $app object for the index.php file to use.

Now, we are ready to use our Rectangle class. We'll go back to the work we did with the "Objects and methods" lesson and copy just the class declaration for the Rectangle class into a file. While we’re at it, we'll update the class declaration to use a constructor, private properties and getters and setters for them. Let's save the file as Rectangle.php in a new folder in our project folder called src, which is short for source. It should look like this:

src/Rectangle.php
<?php 
    class Rectangle 
    {
        private $length;
        private $width;

        function __construct($length, $width)
        {
            $this->length = $length;
            $this->width = $width;
        }

        function isSquare()
        {
            if($this->length == $this->width) {
                return true;
            } else {
                return false;
            }
        }

        function getArea()
        {
            return $this->length * $this->width;
        }

        function setLength($new_length)
        {
            $this->length = (float) $new_length;
        }

        function getLength()
        {
            return $this->length;
        }

        function setWidth($new_width)
        {
            $this->width = (float) $new_width;
        }

        function getWidth()
        {
            return $this->width;
        }
    }
?>

We tell Silex where to find our class declaration by using require_once a second time at the top of our app.php file following the path to our vendor/autoload.php file.

app/app.php
require_once __DIR__."/../vendor/autoload.php";
require_once __DIR__."/../src/Rectangle.php";

Now when we instantiate a Rectangle, Silex will know what we’re talking about.

Next, let's create a route to the form where users fill out a length and a width for the rectangle they're creating. Add this route to your app.php file right above the last line which says return $app;:

app/app.php
$app->get("/new_rectangle", function() {
        return "
        <!DOCTYPE html>
        <html>
        <head>
            <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'>
            <title>Make a rectangle!</title>
        </head>
        <body>
            <div class='container'>
                <h1>Geometry Checker</h1>
                <p>Enter the dimensions of your rectangle to see if it's a square.</p>
                <form action='/view_rectangle'>
                    <div class='form-group'>
                      <label for='length'>Enter the length:</label>
                      <input id='length' name='length' class='form-control' type='number'>
                    </div>
                    <div class='form-group'>
                      <label for='width'>Enter the width:</label>
                      <input id='width' name='width' class='form-control' type='number'>
                    </div>
                    <button type='submit' class='btn-success'>Create</button>
                </form>
            </div>
        </body>
        </html>
        ";
    });

We are simply returning the same form which we used in the Objects and methods lesson, except for two details.

  • First, all uses of double quotes inside of the HTML have been replaced with single quotes.

The double quotes are used only to mark the beginning and the end of the HTML string we are returning. By the way, we will be learning a better way to display HTML inside of Silex later, but for right now focus on understanding how to use classes within the structure of a Silex app.

  • Second, we have changed the form action to send the form to the path /view_rectangle, instead of directly to Rectangle.php.

This /view_rectangle is the route we will make next. It will allow us to use the 2 numbers from the form to create and display a Rectangle object. Add this to your app.php file underneath the route returning the form, but above the last line where we return $app;:

app/app.php
$app->get("/view_rectangle", function() {
    $my_rectangle = new Rectangle($_GET['length'], $_GET['width']);
    $area = $my_rectangle->getArea();
    if ($my_rectangle->isSquare()) {
        return "<h1>Congratulations! You made a square! Its area is $area.</h1>";
    } else {
        return "<h1>Sorry! This isn't a square. Its area is $area.</h1>";
    }
});

This is basically the same code that we had in the rest of the old Rectangle.php file outside of the class declaration. We instantiate a new Rectangle using its constructor, passing it the length and width values from our form. Then we call the Rectangle’s getArea method and store the return value in the variable $area. Lastly, we call the Rectangle’s isSquare method as the condition to an if statement, and it determines which message to display with the $area - either “Congratulations! You made a square!” or “This isn't a square.” Really, the only thing we had to change is that instead of using echo to print to the browser, we return what we want to print from the route's function.

Let's do another one to make sure we're really solid on this. Let's port our CD store into Silex. First we'll follow the same steps to set up our project folder.

  • Create project folder with empty folders app and web inside of it.
  • Create composer.json file in the top level of the project folder. (Just require “Silex” for now, same as before)
composer.json
{
    "require": {
        "silex/silex": "~1.1"
    }
}
  • Run $ composer install.
  • Create index.php inside of the web folder, with the same text inside of it.
web/index.php
<?php
    $website = require_once __DIR__.'/../app/app.php';
    $website->run();
?>
  • Start your server inside of web: php -S localhost:8000
  • Create app.php inside of the app folder. Start with this as your basic skeleton:
app/app.php
<?php
    require_once __DIR__."/../vendor/autoload.php";

    $app = new Silex\Application();

    $app->get("/", function() {
        return "Home";
    });

    return $app;
?>
  • Create src folder for classes.

Now let's add the file Cd.php into our source folder, and just like our Rectangle.php file, we put only our CD class declaration inside of it. It includes a constructor as well as getters and setters for all properties, and all properties are set to private.

src/Cd.php
<?php 
    class CD
    {
        private $title;
        private $artist;
        private $cover_art;
        private $price;

        function __construct($album_name, $band_name, $image_path, $album_price = 10.99)
        {
            $this->title = $album_name;
            $this->artist = $band_name;
            $this->cover_art = $image_path;
            $this->price = $album_price;
        }

        function setPrice($new_price)
        {
            $float_price = (float) $new_price;
            if ($float_price != 0) {
                $formatted_price = number_format($float_price, 2);
                $this->price = $formatted_price;
            }
        }

        function getPrice()
        {
            return $this->price;
        }

        function setTitle($new_title)
        {
            $this->title = $new_title;
        }

        function getTitle()
        {
            return $this->title;
        }

        function setArtist($new_artist)
        {
            $this->artist = $new_artist;
        }

        function getArtist()
        {
            return $this->artist;
        }

        function setCoverArt($new_cover_art)
        {
            $this->cover_art = $new_cover_art;
        }

        function getCoverArt()
        {
            return $this->cover_art;
        }

    }
?>

Now, we will use require_once to include our Cd’s class file inside of our app.php file. Add this line to the top of app.php underneath the require_once __DIR__."/../vendor/autoload.php"; statement so that it looks like this:

app/app.php
<?php
    require_once __DIR__."/../vendor/autoload.php";
    require_once __DIR__."/../src/Cd.php";
?>

Lastly, let's add a route to display the CDs on the home page at the root path. Here is the rest of the app.php file. This should go after the require_once statements.

app/app.php
    $app = new Silex\Application();


    $app->get("/", function() {
        $first_cd = new CD("Master of Reality", "Black Sabbath", "images/reality.jpg", 10.99);
        $second_cd = new CD("Electric Ladyland", "Jimi Hendrix", "images/ladyland.jpg", 10.99);
        $third_cd = new CD("Nevermind", "Nirvana", "images/nevermind.jpg", 10.99);
        $fourth_cd = new CD("I don't get it", "Pork Lion", "images/porklion.jpg", 49.99);
        $cds = array($first_cd, $second_cd, $third_cd, $fourth_cd);

        $output = "";
        foreach ($cds as $album) {
            $output = $output . "<div class='row'>
                <div class='col-md-6'>
                    <img src=" . $album->getCoverArt() . ">
                </div>
                <div class='col-md-6'>
                    <p>" . $album->getTitle() . "</p>
                    <p>By " . $album->getArtist() . "</p>
                    <p>$" . $album->getPrice() . "</p>
                </div>
            </div>
            ";
        }
        return $output;
    });


    return $app;

The first thing we do in our route is instantiate the CDs and put them in an array.

Then we want to print out some HTML displaying the CDs. So, we start with an empty string called $output.

Then, we loop through the array of CDs. We call each one's getter methods for title, artist, price, and image path properties.

Instead of printing the HTML with echo we need to return a string. The string to print is constructed in the loop by using the . operator to add the HTML for each CD into our $output variable with the values stored in each CD object's properties. Then after the loop has finished going through all the CDs, we just return $output.

We use double quotes at the beginning and end of each piece of HTML, but method calls must be outside of double and single quotes. Variables are evaluated inside of double quotes, but not method calls.

Now we can go to localhost::8000 and view all of our CDs. Just be sure to copy your images folder into the web folder since it is your document root. Otherwise the pictures won't load. All of your images and other public resources should be inside this folder.

To use objects in Silex, first set up your app the same way we did in the previous lesson. Then:

  • Create a src directory inside of your project folder.
  • Put your each class declaration in its own file with no other logic and then put the file in your src directory. For example: project_name/src/MyClass.php
  • Load the class into your app.php file using require_once. For example:
app/app.php
require_once __DIR__."/../vendor/autoload.php";
require_once __DIR__."/../src/MyClass.php";

Now inside of our routes we can instantiate and call methods on our objects the same way we're used to.

When instantiating an object from a form, you can use the $_GET superglobal to access the data entered into the form, just like in previous exercises. For example:

app/app.php
$app->get("/view_rectangle", function() {
    $my_rectangle = new Rectangle($_GET['length'], $_GET['width']);
    $area = $my_rectangle->getArea();
    if ($my_rectangle->isSquare()) {
        return "<h1>Congratulations! You made a square! Its area is $area.</h1>";
    } else {
        return "<h1>Sorry! This isn't a square. Its area is $area.</h1>";
    }
}