Get up to 80 % extra points for free! More info:

Lesson 13 - Object-oriented hit counter in PHP - continuation

In the previous lesson, Object-oriented hit counter in PHP and PDO, we started programming a web hit counter. We created a database table as well as cover how to work with databases using the PDO driver.

Database wrapper

Working with databases in PDO, as we saw last time, only has one major disadvantage. Surely you agree that we'd most likely need to use a single database from multiple places in the application. Well with PDO, you have to re-connect every single time or pass the connection instance manually. If you recall static members, they allow us to share data between the entire application. Due to these circumstances, the best thing to do is create a database wrapper. In other words, a static utility class used to wrap the calls to the PDO instance.

Now make a new folder and name it "classes". Then, make a new file in the folder you just now created, name it Database.php, and add the following content:

<?php

class Database
{

    private static $connection;

    private static $settings = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
    );

    public static function connect($host, $user, $password, $database)
    {
        if (!isset(self::$connection))
        {
            self::$connection = @new PDO(
                "mysql:host=$host;dbname=$database",
                $user,
                $password,
                self::$settings
            );
        }
        return self::$connection;
    }

    public static function query($sql, $parameters = array())
    {
        $query = self::$connection->prepare($sql);
        $query->execute($parameters);
        return $query;
    }

}

The class is very short and contains two private static attributes. One of them is the desired PDO settings, and the second one is a PDO instance, which is what we are going to share throughout the application.

As for the static methods, one is used to connect to a database, which will create a PDO instance and store it into the static $connection variable. The connected instance will remain there until the script terminates. The second static method allows us to execute a query using its parameters. The query would then be passed to the PDO instance, after which the method will return its result. The wrapper is very simple, however, it should do the trick for now (we'll keep on improving it in the next couple of lessons).

VisitManager class

Now we need a brand new class to manage visits and all their aspects. We'll need a method that stores visits, more precisely, one that stores page views. Also, we'll need a method that returns the total number of page views and unique visitors. Last of all, we'll need to add a method that returns the number of visits and views in the last few days.

Our class will look something like this:

<?php

class VisitManager
{

    public function addPageview()
    {
        Database::query('
            INSERT INTO `pageview`
            (`ip`, `created`)
            VALUES (?, NOW())
        ', array($_SERVER['REMOTE_ADDR']));
    }

    public function totalPageviews()
    {
        $result = Database::query('
            SELECT COUNT(*) AS `cnt`
            FROM `pageview`
        ');
        $data = $result->fetch();
        return $data['cnt'];
    }

    public function pageviewsFor($days)
    {
        $result = Database::query('
            SELECT COUNT(*) AS `cnt`
            FROM `pageview`
            WHERE `created` > (NOW() - INTERVAL ? DAY)
        ', array($days));
        $data = $result->fetch();
        return $data['cnt'];
    }

    public function totalUips()
    {
        $result = Database::query('
            SELECT COUNT(DISTINCT `ip`) AS `cnt`
            FROM `pageview`
        ');
        $data = $result->fetch();
        return $data['cnt'];
    }

    public function uipsFor($days)
    {
        $result = Database::query('
            SELECT COUNT(DISTINCT `ip`) AS `cnt`
            FROM `pageview`
            WHERE `created` > (NOW() - INTERVAL ? DAY)
        ', array($days));
        $data = $result->fetch();
        return $data['cnt'];
    }

    public function printStatistics()
    {
        echo('<table>');
            echo('<tr>
                <td>Total pageviews</td>
                <td>' . $this->totalPageviews() . '</td>
            </tr>');
            echo('<tr>
                <td>Total UIPs</td>
                <td>' . $this->totalUips() . '</td>
            </tr>');
            echo('<tr>
                <td>Pageviews last month</td>
                <td>' . $this->pageviewsFor(30) . '</td>
            </tr>');
            echo('<tr>
                <td>UIPs last month</td>
                <td>' . $this->uipsFor(30) . '</td>
            </tr>');
            echo('<tr>
                <td>Pageviews last week</td>
                <td>' . $this->pageviewsFor(7) . '</td>
            </tr>');
            echo('<tr>
                <td>UIPs last week</td>
                <td>' . $this->uipsFor(7) . '</td>
            </tr>');
        echo('</table>');
    }

}

Here, we use our static database wrapper. The class has six methods, out of which the first is used to store a new page view. We've already gone over the query used in this method, the only difference is that we use our database wrapper. The second method returns the total number of page views, whereas the third returns the number of page views in the last few days. The fourth and fifth methods do the same thing, but with UIPs. All of the methods are public, in case we want to get a statistic independently from somewhere in our application.

If we needed to get a value from the result, we'd have to call the fetch() or fetchAll() method on it first.

Fetch() returns the next row in a result, which the query returned, as an array. Whereas the fetchAll() returns an array of all of the rows. We'll call fetch() when all we're interested in is a single row, and fetchAll() when we want all of the rows. We can also call rowCount() on the result, which returns the number of rows in a result. Just make sure you don't mistake it for the SQL COUNT command! To determine the number of unique IP addresses we use the DISTINCT clause. If you've already read through our MySQL course, you know that this clause works with unique rows.

We use DATETIME date type and INTERVAL clauses to manipulate date and time. Occasionally, you may encounter the cases where the int type is used to represent date and time, where the date is then stored as the total number of seconds since 01/01/1970. You should avoid this type of notation since it's not human readable and is dependent on time zones.

Launching the application

Now let's put it all together! Go back to index.php, add a basic HTML structure as well as a PHP autoloader (so we won't have to load classes manually), and finally connect to the database.

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>OOP in PHP at ICT.social</title>
    </head>

    <body>
    <h1>Web hits</h1>
        <?php
            mb_internal_encoding("UTF-8");

            function loadClass($class)
            {
                require("classes/$class.php");
            }

            spl_autoload_register("loadClass");

        Database::connect('localhost', 'root', '', 'webcounter_db');

    ?>
    </body>
</html>

The connection is stored in a static variable, so we can manipulate it as we please since it will stay in memory until the script terminates.

To keep things simple, we'll insert both the counting visit and printing table parts into index.php. In real life determining the number of visits is a rather expensive operation due to the COUNT clause being used on a huge amount of rows. With this in mind, we will only call it from an administration standpoint, to see how our site is running. However, we will store visits on every single page:

$visitorsManager = new VisitManager();
$visitorsManager->addPageview();
$visitorsManager->printStatistics();

We didn't style the result, so it doesn't look very pretty (it works just fine, though):

Your page
localhost

In the next lesson, Solved tasks for OOP in PHP lessons 12-13, we will get into some more OOP techniques. Today's project can be downloaded in the attachment below the article.

In the following exercise, Solved tasks for OOP in PHP lessons 12-13, we're gonna practice our knowledge from previous lessons.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 62x (3.55 kB)
Application includes source codes in language PHP

 

Previous article
Object-oriented hit counter in PHP and PDO
All articles in this section
Object-Oriented Programming in PHP
Skip article
(not recommended)
Solved tasks for OOP in PHP lessons 12-13
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities