CHAPTER 8

image

Showing Blog Entries

The entry editor is well on its way. You can use it to create new blog entries, which will be saved in the database. You are probably slowly developing some understanding of the model-view-controller (MVC) paradigm, but surely, you need some more practice to become comfortable using it.

In this chapter, you will learn how to display all blog entries on your index.php page. In the process, you will revisit much of what you have seen or learned so far. I will also introduce the following new topics:

  • Making a new front controller
  • Updating your table data gateway: adding a method to your Blog_Entry_Table class
  • Iterating through a data set from a database table
  • Creating a model, view, and controller for showing blog entries

Creating a Public Blog Front Page

Later in this book, you will learn how to hide your administration module behind a login. You wouldn’t want everybody to be able to write new blog entries on your blog, would you? When you get around to it, admin.php will be reserved for authorized users only. The public face of your blog will be index.php. Create a public face. Create a new file index.php, as follows:

<?php
//complete code for index.php
error_reporting( E_ALL );
ini_set( "display_errors", 1 );
include_once "models/Page_Data.class.php";
$pageData = new Page_Data();
$pageData->title = "PHP/MySQL blog demo example";
$pageData->addCSS("css/blog.css");
 
$dbInfo = "mysql:host=localhost;dbname=simple_blog";
$dbUser = "root";
$dbPassword = "";
$db = new PDO( $dbInfo, $dbUser, $dbPassword );
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
 
$pageData->content .= "<h1>All is good</h1>";
$page = include_once "views/page.php";
echo $page;

Save the index.php file. Remember that MySQL and Apache should run before you try to load anything through your localhost. You can start MySQL and Apache through the XAMPP control panel. Once MySQL and Apache are running, you can load http://localhost/blog/index.php in your browser and check for yourself that everything works so far. You should expect to see “All is good” in your browser. If your database credentials are invalid, you will see an exception displayed in your browser.

Creating a Blog Controller

By default, you want blog entries to be displayed in an index page. You can start with the smallest possible step and create a super-simple blog controller. Create a new file blog.php in the controllers folder:

<?php
//complete code for controllers/blog.php
return "<h1>blog entries coming soon!</h1>";

With a preliminary blog controller ready, you can load it from index.php to test that the controller is loaded. Update index.php:

//partial code for index.php
 
//changes begin here
//comment out initial test message
//$pageData->content .= "<h1>All is good</h1>";
//include blog controller
$pageData->content .=include_once "controllers/blog.php";
//no changes below here
 
$page = include_once "views/page.php";
echo $page;

Save the blog controller and your index.php. Reload http://localhost/blog/index.php in your browser. You should see an output like this:

 blog entries coming soon!

When you see that output in your browser, you know that your index.php (your front controller) loads your blog controller.

Getting Data for All Blog Entries

You want to be able to show a list of all blog entries found in the database. How about showing the title, the first 150 characters of the entry_text, and a Read more link for each of the blog entries?

Because you’re determined to stick to clean code with an MVC approach, you already know that you’ll need a model, a view, and a controller. You already have a bare-bones blog controller. You’ll gradually improve the existing blog controller. You also have a Blog_Entry_Table class that provides a single point of access to the blog_entry database table. That will be your model, though it needs to be updated a bit to give you what you want. You don’t have a view for listing all entries yet.

Begin by getting the right data from the blog_entry table. Declare a new method in models/Blog_Entry_Table.class.php, as follows:

//partial code for models/Blog_Entry_Table.class.php
 
//declare a new method inside the Blog_Entry_Table class
public function getAllEntries () {
    $sql = "SELECT entry_id, title,
            SUBSTRING(entry_text, 1, 150) AS intro
            FROM blog_entry";
    $statement = $this->db->prepare( $sql );
    try {
        $statement->execute();
    } catch ( Exception $e ) {
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage);
    }
    return $statement;
}

Image Caution  Be careful where you write this code. You can’t just place it anywhere in the Blog_Entry_Table.class.php file. You should write it between the two curly braces that delimit the class definition—inside the class definition.

Perhaps you’re wondering what it means to write the new code inside the class definition? In the following code example, you can see two methods declared inside one class definition:

//class definition starts here
class Something {
 
    //all methods must be declared inside the class definition
    public function someMethod () {
        //
    }
 
    public function someOtherMethod () {
        //
    }
 
} //class definition ends here

The getAllEntries() method will return a PDOStatement object, through which you can get access to all blog entries, one at a time. The SQL statement involves some SQL you haven’t seen before, so it might be a bit tricky to understand initially. Let’s go through it step by step.

Using an SQL SUBSTRING Clause

The first thing to notice is that the SELECT statement selects three columns—entry_id, title, and entry_text—from the blog_entry table. But you’re not selecting everything from the entry_text column. You’re only selecting the first 150 characters. You do that with a SUBSTRING() function. The general syntax for the MySQL SUBSTRING() function is as follows:

SUBSTRING( string, start position, length )

The SUBSTRING() returns a part of a string: a substring. The string argument indicates which string to return a part of. The start position argument indicates at which position to start the substring. The length argument specifies how long a substring to return. In the SELECT statement, you’re really selecting the first 150 characters from the string found in the entry_text field.

Using an SQL Alias

With SQL, you can use an alias to rename a table or a column. In the SQL used to select all blog entries, you have renamed the substring to intro. It’s not strictly necessary to rename the column. Without the AS clause, you would still have a SELECT statement that returns data from three columns. The returned data set might be something like the following table:

entry_id

title

SUBSTRING(entry_text, 1, 150)

1 My first entry Blah . . .
2 My second entry Blah, blah . . .

I think you will agree that the third column has a strange name. Strange names in code are bad, because the code becomes harder to read and understand. By renaming the returned column using the AS keyword, you get a table that is easier to read and understand, as follows:

entry_id

title

intro

1 My first entry Blah . . .
2 My second entry Blah, blah . . .

Testing Your Model

It is important to look for small steps when developing something with code. If you write only a little code between tests, it will be easier to spot coding errors early. Do a little test to see if the getAllEntries() method returns the right data. The Blog_Entry_Table object will be used from the blog controller, because the blog controller will load the blog model and the blog view, in the process of generating an output for the browser. So, the blog controller is the logical place to write a test. Here’s some code to test getAllEntries() from controllers/blog.php:

<?php
//complete code for controllers/blog.php
include_once "models/Blog_Entry_Table.class.php";
$entryTable = new Blog_Entry_Table( $db );
 
//$entries is the PDOStatement returned from getAllEntries
$entries = $entryTable->getAllEntries();
//fetch data from the first row as a StdClass object
$oneEntry = $entries->fetchObject();
//print the object
$test = print_r( $oneEntry, true );
 
//return the printed object to index to see it in browser
return "<pre>$test</pre>";

Using print_r() for Inspecting an Object

The preceding code uses a PHP function print_r() to inspect an object. You have already used print_r() to inspect two superglobal arrays, $_POST and $_FILES. You can use the same function to inspect objects. If you save controllers/blog.php and load http://localhost/blog/index.php in your browser, you should get an output a bit like the following, except you probably have something else saved in your title and intro:

stdClass Object (
     [entry_id] => 1
     [title] => Testing title
     [intro] => test
)

Looking closely at the output, you can see that the printed $oneEntry is an StdClass object with three properties: entry_id, title, and intro. The bigger question is whether that’s what you want. Was the test successful or not?

The test was successful! The $oneEntry variable should hold an StdClass object with exactly those three properties. You’re looking at a PHP object representing one row of data; you’re looking at your blog entry model. The $oneEntry variable should represent one row of data received from MySQL through PDO. The three properties are created by your SELECT statement from the getAllEntries() method:

//the SQL statement used in Blog_Entry_Table->getAllEntries()
SELECT entry_id, title,
SUBSTRING(entry_text, 1, 150) AS intro
FROM blog_entry

All SQL SELECT statements will return a new, temporary table with the columns specified in the SELECT clause. The returned table will be populated with data found in the table specified in the FROM clause.

The getAllEntries() method will query your database and return a PDOStatement object. Every time you call the PDOStatement’s fetchObject() method, the PDOStatement will return an StdClass object representing one row of data.

Even absolute beginners can easily understand that developing in small steps is sensible. It’s easy to see that an error is easier to spot in fewer lines of code. It’s also easy to understand that if a piece of code has an undetected error, subsequent code might end up with more errors, because any subsequent code has to collaborate with the code with the original error.

The result is code with multiple errors in multiple places. The result is code errors that hide underlying code errors. The result is difficult debugging! This is how a coding error becomes a bug: when you write your code around a tiny, undetected error.

The only countermeasure is to write code in small steps and test your code between every step. The problem is that it can be difficult for beginners to test whether their code works. Beginners don’t always realize exactly what their code is doing or what it should be doing. Beginners often experiment with their code without really knowing what to expect—that’s what it’s like to be a beginner! Your ability to predict PHP behavior and formulate small tests will only improve through experience and reflection.

Image Note  There is an approach to development called test-driven development. It’s based on writing formal tests for every unit of code and test units individually. It’s hardly a topic for beginners, but it could be useful for you to know that test-driven development exists.

I’ll continue to show you many examples of how you can create small, informal tests and how you can write as little code as possible between steps.

Preparing a View for All Blog Entries

With a model prepared, loaded, and tested from your controller, it’s time to create a view. This view is supposed to list all blog entries. The number of blog entries is likely to change, so it would be a bad idea to create a view for a specific number of entries.

The view should automatically change to accommodate however many—or few—blog entries are found in the database. You need to iterate over blog entries with a loop. Create a new file, views/list-entries-html.php, and have it loop through entries, as follows:

<?php
//complete code for views/list-entries-html.php
$entriesFound = isset( $entries );
if ( $entriesFound === false ) {
    trigger_error( 'views/list-entries-html.php needs $entries' );
}
 
//create a <ul> element
$entriesHTML = "<ul id='blog-entries'>";
 
//loop through all $entries from the database
//remember each one row temporarily as $entry
//$entry will be a StdClass object with entry_id, title and intro
while ( $entry = $entries->fetchObject() ) {
    $href  = "index.php?page=blog&amp;id=$entry->entry_id";
    //create an <li> for each of the entries
    $entriesHTML .= "<li>
        <h2>$entry->title</h2>
        <div>$entry->intro
            <p><a href='$href'>Read more</a></p>
        </div>
    </li>";
}
//end the <ul>
$entriesHTML .= "</ul>";
return $entriesHTML;

Just take a minute to look at that wonderful while loop before actually using it in your application. It will dynamically create <li> elements for however many rows are found in the blog_entry database table. If there is one blog entry in your database, there will be one <li>. If there are ten blog entries in your database, there will be ten <li> elements.

Notice the comment about which properties an $entry should have. It is easy to forget which properties should be available, so it can be helpful to write a comment about it.

Hooking Up View and Model

The final step is to hook up your view with the data from your model. It doesn’t take much code, but it will produce a radically different output in your browser. Update your code in controllers/blog.php:

<?php
//complete code for controllers/blog.php
include_once "models/Blog_Entry_Table.class.php";
$entryTable = new Blog_Entry_Table( $db );
$entries = $entryTable->getAllEntries();
 
//code changes start here
//test completed - delete of comment out test code
//$oneEntry = $entries->fetchObject();
//$test = print_r($entryTable, true );
 
//load the view
$blogOutput = include_once "views/list-entries-html.php";
return $blogOutput;

That’s it! Load http://localhost/blog/index.php in your browser, and you should see a <ul> with separate <li> elements for each blog entry. You could visit http://localhost/blog/admin.php?page=editor, to create a new entry, and then reload http://localhost/blog/index.php in your browser, to see the newly created blog entry listed. Your blogging system is really starting to look like a proper blog.

It’s very tempting to click Read more, isn’t it? Don’t you just want to click it and read a blog entry? Well, clicking Read more at this point will not have a great impact. You haven’t written the code to display all content of individual blog entries, so nothing will change when you click.

Responding to User Requests

You want to be able to show all content for one blog entry when a user clicks Read more. You will have to select individual blog entries by their entry_id, by the primary key. It is already available in the code; you just need to find it.

Which part of your code should respond when a user clicks Read more: model, view, or controller? The controller! A controller is meant for handling user interactions. Clicking a link is a user interaction. So, you should work on your blog controller to deal with users clicking Read more. Here’s a small code change in controllers/blog.php that will output the primary key of the clicked blog entry:

<?php
//complete code for controllers/blog.php
include_once "models/Blog_Entry_Table.class.php";
$entryTable = new Blog_Entry_Table( $db );
 
//new code starts here
$isEntryClicked = isset( $_GET['id'] );
if ($isEntryClicked ) {
    //show one entry . . . soon
    $entryId = $_GET['id'];
    $blogOutput = "will soon show entry with entry_id = $entryId";
} else {
    //list all entries
    $entries = $entryTable->getAllEntries();
    $blogOutput = include_once "views/list-entries-html.php";
}
//end of changes
 
return $blogOutput;

Load http://localhost/blog/index.php in your browser to see what changed. By default, you’ll still see a list of all entries, but if you click Read more on the first entry, you’ll see a different output, as follows:

will soon show entry with entry_id = 1

The entry_id is the primary key of blog entries. Now that your code knows the primary key of the clicked blog entry, it will be an almost trivial task to display it. Before tackling that, I think you should take a little time to reflect: why can you find the entry_id in $_GET['id'] when Read more is clicked?

The Read more link is created with a somewhat special href. If you click such a link in your browser, you can see the requested URL in the browser’s address bar. It’ll be something like the following:

http://localhost/blog/index.php?page=blog&id=1

Notice that there are two URL variables encoded: one called page and another called id. The two URL variables are separated with an & (ampersand) character. One URL can hold multiple URL variables, as long as each URL variable is separated with the & character.

You can probably work out that the id URL variable holds the entry_id attribute of the clicked entry. That’s how you can find the primary key of a clicked entry; it’s encoded in the URL. But how did it get encoded there? The answer lies in views/list-entries-html.php. Take an extra look at the href for the <a> element:

//partial code for views/list-entries-html.php
//no code changes – please just read the code again
while ( $entry = $entries->fetchObject() ) {
    $href  = "index.php?page=blog&amp;id=$entry->entry_id";
    $entriesHTML .= "<li>
        <h2>$entry->title</h2>
        <div>$entry->intro
            <p><a href='$href'>read more</a></p>
        </div>
    </li>";
}

And there it is, plain to see. The URL variable id gets its value from the corresponding entry’s entry_id attribute. It might be a little strange to look at the &amp;.

It’s no big deal. An & character is called an ampersand, and &amp; is an HTML character entity representing an ampersand character. An HTML character entity is a short code representing a special character.

Image Note  You can read more about HTML entities at www.w3schools.com/html/html_entities.asp.

Getting Entry Data

It is time for you to tackle the trivial problem of displaying the entry. To do that, you get data from the blog_entry table. You already have a class that provides access to the blog_entry table. You can continue to use that, so you have a single point of access to the table. You can declare a new method in models/Blog_Entry_Table.class.php.

You already have the entry_id available in the blog controller. So, you can declare a method that takes an entry_id as argument and returns an StdClass object with all the content for the corresponding blog entry, as in the following:

//partial code for models/Blog_Entry_Table.class.php
//declare a new method inside the class code block
//do not change any existing methods
public function getEntry( $id ) {
    $sql = "SELECT entry_id, title, entry_text, date_created
            FROM blog_entry
            WHERE entry_id = ?";
    $statement = $this->db->prepare( $sql );
    $data = array( $id );
    try{
        $statement->execute( $data );
    } catch (Exception $e) {
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage );
    }
    $model = $statement->fetchObject();
    return $model;
}

You can see that this new method is quite similar to the other methods inside the Blog_Entry_Table class. First, you declare an SQL string. Next, you use the prepare() method to convert the SQL string to a PDOStatement object and try() to execute() the statement. Finally, you fetch the first row of data from the returned MySQL table and return it as an StdClass object.

Note the use of a prepared statement. Remember to always use a prepared statement when you are creating SQL statements, using input received from the browser. The $id came from a URL variable and as such, it should be treated as unsafe. A malicious hacker might try to exploit it and attempt an SQL injection attack. A prepared statement stops such attempts.

You’re using a prepared statement with unnamed placeholders (represented by ? characters). To replace the placeholder with an actual value, you create an array of values to use and pass the array to the execute() method.

You’ve already seen this way of using a prepared statement—you used it for building the entry editor. Feel free to consult Chapter 7 to refresh your memory and deepen your understanding of the concept.

Creating a Blog View

To show an entry, you need a view for it, a view that supplies an HTML structure and merges it with data for the entry. Create a new file, views/entry-html.php, as follows:

<?php
//complete source code for views/entry-html.php
//check if required data is available
$entryDataFound = isset( $entryData );
if ( $entryDataFound === false ) {
    trigger_error('views/entry-html.php needs an $entryData object'),
}
//properties available in $entry: entry_id, title, entry_text, date_created
return "<article>
    <h1>$entryData->title</h1>
    <div class='date'>$entryData->date_created</div>
    $entryData->entry_text
</article>";

The preceding code is a repetition of the approach you have seen a few times since developing the poll. The essence is quite basic: merge some data stored in an StdClass object with a predefined HTML structure. The view requires an StdClass object saved in a variable called $entryData. So, the first few lines of code check the availability of $entryData. If it is not found, the code will trigger a custom error, so it will be easy to find and correct the mistake.

Displaying an Entry

You’ve got the model; you’ve got the view. The final step is to update your blog controller. It is responsible for fetching entry data from the model, sharing it with the entry view, and returning the resulting HTML string to index.php, where it will be displayed. Your controller is only two lines of code away from completion:

<?php
//complete code for controllers/blog.php
include_once "models/Blog_Entry_Table.class.php";
$entryTable = new Blog_Entry_Table( $db );
$isEntryClicked = isset( $_GET['id'] );
if ($isEntryClicked ) {
    $entryId = $_GET['id'];
    //new code begins here
    $entryData = $entryTable->getEntry( $entryId );  
    $blogOutput = include_once "views/entry-html.php";
    //end of code changes
} else {
    $entries = $entryTable->getAllEntries();
    $blogOutput = include_once "views/list-entries-html.php";
}
return $blogOutput;

Test your progress by loading http://localhost/blog/index.php?page=blog in your browser. Click Read more, and you should see the full content of the clicked blog entry displayed in your browser.

You have a functional blog, complete with an index.php for normal users and admin.php through which you can create new blog entries. Now would be a good time to celebrate your progress. You have come a long way since Chapter 1. You know something about object-oriented programming with PHP. You even have some experience with a few design patterns. You know how to work with databases. You are no longer an absolute beginner.

Code Smell: Duplicate Code

Can you smell it? There is a bad smell coming from your code. It’s one of those classic code smells every coder knows about. And it’s one of those things you should learn to avoid, as your proficiency with code grows.

Image Note  Find a long list of typical code smells at http://en.wikipedia.org/wiki/Code_smell.

Duplicate code is when identical or similar code exists in multiple places in your code. Do you already know where to find the smell? It’s in models/Blog_Entry_Table.class.php. Here’s an example:

//partial code for models/Blog_Entry_Table.class.php
public function getEntry( $id ){
    $sql = "SELECT entry_id, title, entry_text, date_created
            FROM blog_entry
            WHERE entry_id = ?";
    $statement = $this->db->prepare( $sql );
    $data = array( $id );
    try {
        $statement->execute( $data );
    } catch (Exception $e){
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage);
    }
    $model = $statement->fetchObject();
    return $model;
}

There are a couple of other methods in the Blog_Entry_Table class. They all prepare() a PDOStatement and try() to execute() it. So, in all three methods, you can find five to seven lines of code that are nearly identical. That’s bad!

Staying DRY with Curly

Duplicate code is bad for a number of reasons. One is that you are simply using more lines than necessary. Your code is unnecessarily long. Longer code is worse, because more code invariably means more errors. Less code means fewer errors. Another reason duplicate code is bad is that chances are you’ll be changing the code you write. Someday, you’ll want to make some changes. If you have identical or very similar code spread out in ten different methods, you will have to make identical or very similar code changes in ten different methods. If you keep the code in a separate method, you will only have to change the code in one method, and it will affect the other ten methods automatically.

It’s really just another case of coding by Curly’s law, or at least a variant of Curly’s law. Curly’s original law was: Do one thing. This particular variant should perhaps be: Do one thing once. There’s another geek expression for it:  staying DRY. DRY is an acronym that means “don’t repeat yourself.”

Refactoring with Curly

Refactoring is the process of changing your code without changing what it does. It’s a big deal for coders. You should refactor your code when you realize the project requirements have outgrown the code architecture, or, in other words, when the code architecture doesn’t support the features your project needs in a beautiful way.

It is time to refactor the Blog_Entry_Table class, so it becomes more DRY. Let’s begin by encapsulating the code that prepares an SQL statement into a separate method. Declare a new method in models/Blog_Entry_Table.class.php, as follows:

//Partial code for models/Blog_Entry_Table.class.php
 
//declare a new method in the Blog_Entry_Table class
//$sql argument must be an SQL string
//$data must be an array of dynamic data to use in the SQL
public function makeStatement ( $sql, $data) {
    //create a PDOStatement object
    $statement = $this->db->prepare( $sql );
    try{
        //use the dynamic data and run the query
        $statement->execute( $data );
    } catch (Exception $e) {
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage);
    }
    //return the PDOStatement object
    return $statement;
}

With the new method declared, you can try to refactor one of your existing methods to use the new method. You could begin with the most recently declared method, the getEntry() method.

//Partial code for models/Blog_Entry_Table.class.php
 
//edit existing method getEntry
public function getEntry( $id ){
    $sql = "SELECT entry_id, title, entry_text, date_created
            FROM blog_entry
            WHERE entry_id = ?";
    $data = array($id);
    //call the new DRY method
    $statement = $this->makeStatement($sql, $data);
    $model = $statement->fetchObject();
    return $model;
}

Let’s just test that the new method works as intended. Navigate your browser to http://localhost/blog/index.php and click Read more to see one blog entry. If you can see one entry in your browser, you know the refactored getEntry() method works.

See how using the new makeStatement() method makes your getEntry() method a little shorter? You can work through the other methods and replace the hideous duplicate code with a beautiful DRY solution.

There is a little syntactical detail to notice. See how you need the $this keyword when one method calls another method declared in the same class? It’s not really different from using $this to get to a property. In both cases, $this is an object’s reference to itself. It’s the object-oriented way of saying “my.”

Let’s continue refactoring and move on to the saveEntry() method:

//Partial code for models/Blog_Entry_Table.class.php
 
//edit existing method saveEntry
public function saveEntry ( $title, $entry ) {
    $entrySQL = "INSERT INTO blog_entry ( title, entry_text )
                 VALUES ( ?, ?)";
    $formData = array($title, $entry);
    //changes start here
    //$this is the object's way of saying 'my'
    //so $this->makeStatement calls makeStatement of Blog_Entry_Table
    $entryStatement = $this->makeStatement( $entrySQL, $formData );
    //end of changes
}

You can probably trust this to work perfectly, but just to be absolutely sure, you should test. Load http://localhost/blog/admin.php?page=editor in your browser and test that you can still create new blog entries with your entry editor.

Refactoring is truly joyful work, isn’t it? You spot an ugly corner in your code, and you make it beautiful. You really ought to refactor getAllEntries() also. Rewrite the existing function getAllEntries() in models/Blog_Entry_Table.class.php:

//Partial code for models/Blog_Entry_Table.class.php
 
//edit existing method getAllEntries
public function getAllEntries () {
    $sql = "SELECT entry_id, title, SUBSTRING(entry_text, 1, 150) AS intro  FROM blog_entry";
    $statement = $this->makeStatement($sql);
    return $statement;
}

Hang on, there’s a problem here. Do you see it? You’re calling makeStatement() with a single argument, but it needs two arguments. Up until now, you have called it with two arguments. The second argument has been an array of data to use in an SQL string. But in this case, you have no data you want to use in the SQL string. You have no second argument to pass on.

Sometimes, you want to call makeStatement() with one argument, and sometimes you want to call it with two arguments. You want the second argument to be optional. Luckily, PHP has a very easy way to make an argument optional. You can simply declare the argument with a default value, which will be used if nothing is passed. Here’s how to do it:

//Partial code for models/Blog_Entry_Table.class.php
 
//edit existing method makeStatement
//change code: declare a default value of NULL for the $data argument
public function makeStatement ( $sql,$data = NULL){
    //end of code changes
    $statement = $this->db->prepare( $sql );
    try{
        $statement->execute( $data );
    } catch (Exception $e){
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage );
    }
    return $statement;
}

In the preceding code, the argument $data gets a default value of NULL. So, if makeStatement() is called without a second argument, the created PDOStatement object will execute with NULL. That means no dynamic values will replace placeholders in the prepared statement. And that is exactly what you want, because there are no placeholders in the SQL for this statement.

Image Note  You can consult www.w3schools.com/php/php_functions.asp to learn a little more about function arguments with default values.

In the other cases where makeStatement() is called with two arguments, the second argument will be used to replace SQL placeholders with actual values. Using optional arguments is a very powerful concept in code. It can often lead to a clean solution when you are encapsulating nearly duplicate code into a single separate method, just as you’ve just done.

You can trust your code to work when you have tested it. Navigate your browser to http://localhost/blog/index.php to confirm that all entries are listed as before. Remember: to refactor is to change code without changing what it does. So, a successful test is confirmed when the code behaves exactly as it did before refactoring. Refactoring is done with the sole purpose of making the code easier to work with for coders.

Using the Private Access Modifier

The makeStatement() method is a fragile member of the Blog_Entry_Table class. It is only meant to be called internally, and only by other Blog_Entry_Table methods. It is certainly not meant to be called from outside the class. The makeStatement() can be understood as a sub-method used by the other Blog_Entry_Table methods.

Right now, it is possible to call it from the outside. In fact, it can be called from anywhere in your code. That means the makeStatement() method can easily be used in a wrong way, if you or a fellow developer forgets that it should only be called internally. Because of that, makeStatement() is almost an error just waiting to happen.

It is so easy to remedy. You can simply use the private access modifier, and makeStatement() can only be called internally. No other piece of code can ever get access to calling it. Here’s how to use the private access modifier:

//Partial code for models/Blog_Entry_Table.class.php
 
//edit existing method makeStatement
//code change: make it private
private function makeStatement ( $sql, $data = NULL ){
    //end of code changes
    $statement = $this->db->prepare( $sql );
    try {
        $statement->execute( $data );
    } catch (Exception $e) {
        $exceptionMessage = "<p>You tried to run this sql: $sql <p>
                <p>Exception: $e</p>";
        trigger_error($exceptionMessage);
    }
    return $statement;
}

With this tiny change, your Blog_Entry_Table class has improved considerably. The improvement is that it is now impossible to call makeStatement() from the outside. Only a Blog_Entry_Table object can call the method. That means it’s just become a lot harder for you or a fellow coder to use Blog_Entry_Table in a wrong way.

You may have noticed the private keyword before? I have used it for the $db property, without explaining it. A private object property can’t be accessed from the outside. As a rule of thumb, you should declare object properties private by default. Use a different access modifier only if you specifically need to. That way, only the object itself can manipulate its properties.

Remember: The Single Responsibility Principle, also known as Curly’s law, applied to classes. A class should have a single purpose, and all its properties and behaviors should be strictly aligned with that purpose. A class with one responsibility is simpler than a class with many, and a simple class is easier to use than a complex class. By hiding some properties and methods using a private access modifier, you present a public interface that’s even simpler and even easier to use. So, as a rule of thumb, use private by default, public when you need it.

Image Note  There is a third access modifier: protected. In PHP, it is like private, except it can be shared with subclasses through inheritance. You can find a nice tutorial covering inheritance, access modifiers, and other central OOP topics at www.killerphp.com/tutorials/object-oriented-php/.

You have come across a few examples where public object properties have been used—every time you created a view that depends on object properties. You have been using StdClass objects with public properties. You have used such objects to represent a row of data from a database table.

Summary

In this chapter, you have created the public “face” of your blog. In the process, you have seen a little more SQL, and you have had a few extra opportunities to understand MVC.

You have seen how to reuse your own code. The Blog_Entry_Table is now used by admin.php and by index.php. The Blog_Entry_Table is a table data gateway that provides a single point of access from your PHP code to your blog_entry database table.

You have also seen how refactoring can eliminate code smells and how the private access modifier and optional arguments can be used to keep your code DRY.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.178.53