Overview of This Lesson

Refresh this page because I am probably still making changes to it.

In this lesson you'll learn how to create classes and instantiate them using PHP, as well as how to document your classes using PHPDoc.

Pre-Requisites

For this lesson, it's assumed you already have a knowledge of object-oriented programming.

Resources

Introduction to OOP PHP

The concepts of Object-Oriented PHP are the same as the ones you've learned in Java: objects are instances of a class, classes have data members, accessor methods, and mutator methods. Members have modifiers that affect their visibility, such as public and private. You can create a class that inherits from another class (although that won't be covered in this lesson).

As long as you know those basics from your Java course, you should have no problem applying them to PHP.

The main differences between Java OOP classes and PHP OOP classes is the syntax. For example, the code below constructs a Circle object from the Circle class, assigns it a radius, and prints the radius and the circle on the screen:

<?php
    $circle = new Circle;  // construct new Circle instance
    $circle->setRadius(2);  // set radius
    echo $circle->getRadius();  // read/print radius
    echo $circle;  // print circle instance as a string
?>

Note that we don't use "dot notation" when we want to refer to a member of an object. In Java, you would say circle.getRadius(), but in PHP you say $circle->getRadius(). Why do you think we use -> instead of a . in PHP? Because the dot is already used for string concatenation!

For our demonstration, we're going to create the Circle class. The Circle class has the following specification:

Class: Circle
- radius : double
+ Circle()
+ Circle(radius : double)
+ getRadius() : double
+ setRadius(radius : double) : void
+ calcCircumference() : double
+ calcArea() : double
+ toString() : String

Creating Classes and Their Properties

You should keep your OOP classes in separate files in your PHP project: classes are re-usable, so keeping them in separate files allows you to easily use those files in several programs.

I sometimes add more than one class to a file, if those 2 classes are depenent upon each other. For example, I did an application with a class called Invoice that contained an array of LineItem objects. Because Invoice and LineItem had a composition relationship, I defined both Invoice and LineItem in the same file Invoice.php. I always used the two classes together, so I only had to worry about one PHP file. However, if classes are not dependent upon each other, you should store each class in its own file. Note that PHP has no rules about class names matching file names like Java does!

If you want to try the Circle class code, add a new file Circle.php to your PHP project. All of the code that defines the Circle class and its members should go inside that file.

To create a class in PHP, we start with a class header, just like you would in Java:

<?php
    class Circle { 
   
    }
?>

As with other languages, the name of the class should start with an upper-case letter, be self-documenting, and use camel-casing if the name contains more than one word.

In PHP, you don't need a modifier in the class header: classes in PHP are always public. You can't even define private or inner classes in PHP. However, you can apply access modifiers to properties and method members, as you'll see later.

Data Members or Properties

Data members, which are called properties in PHP, can be added to a class by declaring public, private, or protected variables with the option of adding final.

As an example, let's add a private member $radius to our Circle class, to contain the circle's radius:

<?php
    class Circle {

        private $radius;

    }
?>

Accessors and Mutators

As with most OOP languages, private and protected data members require accessors and mutators so that the user of your class can read and write to those members. As with other languages, accessors return the value in a data member and a mutator validates a programmer-specified value - if the value is valid, it is assigned to the data member, otherwise an error occurs.

<?php 
  class Circle {
    private $radius;

    public function getRadius() {
      return $this->radius;
    }

    public function setRadius($radius) {
      if ($radius <= 0) {
        throw new InvalidArgumentException("Radius must be greater than 0.");
      } else {
        $this->radius = $radius;
      }
    }
  }
?>

Notice that we can use the $this keyword if a special variable that always refers to the current instance, so $this->radius refers to "this Circle object's radius" and not the parameter $radius.

Notice also that our accessor and mutator methods are using the public access modifier, so that they are visible outside the Circle class.

Lastly, our mutator throws a generic InvalidArgumentException when the radius value is invalid. PHP doesn't recognize "checked" and "unchecked" exceptions in the same way that languages like Java do. The Exception class is the parent of all PHP exceptions.

The child class LogicException models what we would think of as "unchecked" exceptions: these are exceptions that shouldn't occur and are a result of incomplete/incorrect coding. When these kinds of exceptions occur, the code should be modified so that the error no longer occurs. For example, the InvalidArgumentException is the equivalent to Java's IllegalArgumentException: it occurs when a function receives an invalid argument, such as with our setRadius() example. This exception should never actually occur: when a programmer is using our Circle class, they should make sure they validate anything being passed into the setRadius() method so that the InvalidArgumentException is never thrown.

The child class RuntimeException models exceptions that might occur during runtime, but aren't because of coding errors. For example, an OutOfBoundsException occurs when you try to access an element from a $_GET or $_POST array that doens't exist. Perhaps the value was never sent to the server for some reason: This is a problem that isn't the fault of anything in the code, although a good programmer could add some code to ensure this exception doesn't occur. If a RuntimeException does occur and there's not much you can do to prevent it, it should be logged and a user-friendly and instructional message passed to the user.

Constructor Methods

Classes need constructors: we use constructors to construct/create instances of classes. You create a constructor in PHP using the special __construct() function. Note that there are two underscores in front of the word "construct" !

Unlike Java, you can't override constructors in PHP. If you want the user of your class to have several options for initializing an instance, you can still use a single constructor: since PHP is not a strictly typed language, it's not a huge deal if the user misses an argument when calling a method. You could, in fact, assign defaults to any missed parameters:

<?php
  class Circle {
    
    private $radius

    public function __construct($radius=1.0) {
      if ($radius != 1.0) {  // if it didn't receive default
        $this->setRadius($radius);
      }
    }

    public function getRadius() {
      return $this->radius;
    }

    public function setRadius($radius) {
      if ($radius <= 0) {
        throw new InvalidArgumentException("Radius must be greater than 0.");
      } else {
        $this->radius = $radius;
      }
    }
  }
?>

Here, the constructor accepts a single argument which is defined by the parameter variable $radius. Furthermore, the parameter statement $radius=1.0 says that if the $radius parameter is not supplied, assign it a value of 1.0. Then, inside the function, if the $radius doesn't already contain the default value, it must have been supplied by the programmer when they called the constructor. In that case, the code then calls the setRadius() method to validate and set the radius.

Note that the __construct() function should be public.

A toString() Method

Another magic method often added to classes is the __toString() function. This has the same purpose as the toString() in Java: to return a String representation of the instance.

<?php
  class Circle {
    
    private $radius;

    public function __construct($radius=1.0) {
        if ($radius != 1.0) {
            $this->setRadius($radius);
        }
    }

    public function getRadius() {
        return $this->radius;
    }

    public function setRadius($radius) {
        if ($radius <= 0) {
            throw new Exception("Radius must be greater than 0.");
        } else {
            $this->radius = $radius;
        }
    }

    public function __toString() {
        return __CLASS__." radius=".$this->radius;
    }
  }
?>

In the __toString() method above, I am using the __CLASS__ magic constant. The __CLASS__ magic constant contains the name of the current class (just like Java's getClass().getSimpleName()).

In PHP, adding a __toString() function can be called implicitly in certain circumstances. For example,

echo "A Circle: $myCircleObject";

Will automatically call $myCircleObject->toString() and print the return value. However, if your class doesn't have a defined __toString() function, an error will occur when your code attempts to invoke __toString() implictly or automatically.

Class Constants

Some classes contain constants. Defining a constant in an OOP class in PHP is different from defining a regular constant in PHP: you don't use the define() method to create a constant, you use a normal declaration with the const keyword:

<?php 
  class Circle {
 
    const DEFAULT_RADIUS = 1.0;

    private $radius = DEFAULT_RADIUS;

    public function __construct($radius=1.0) {
        if ($radius != DEFAULT_RADIUS) {
            $this->setRadius($radius);
        }
    }

    public function getRadius() {
        return $this->radius;
    }

    public function setRadius($radius) {
        if ($radius <= 0) {
            throw new InvalidArgumentException("Radius must be greater than 0.");
        } else {
            $this->radius = $radius;
        }
    }

    public function __toString() {
        return __CLASS__." radius=".$this->radius;
    }
}
?>

You don't need the public modifier on a constant: in PHP, class constants are public by default.

PHPDoc

In Java, we use JavaDoc comments to document a class and its public methods. In PHP, we use PHPDoc comments. PHPDoc is not a formal PHP standard yet, but that doesn't mean you shouldn't document with PHPDoc. There are already several tools that can generate API documentation for PHPDocs (a popular favourite is phpDocumentor.

PHPDoc uses the standard @tags that you're already familiar with from JavaDocs, except for a few minor things specific to PHP. For example, the @param and @return tags must include a Type argument so that a developer reading the docs knows what data type a parameter or return value should be. This is because PHP is a weakly-typed language, so variables could technically be any type: it's important to document what type of data a parameter or return value is expected to be.

<?php
/**
 * A class that models a Circle.  A circle has a radius.
 */
class Circle {
    
    /**
     * The default radius of a circle is always 1.0.
     */
    const DEFAULT_RADIUS = 1.0;

    private $radius;

    /**
     * Constructs a Circle object.  If the radius is specified,
     * it must be greater than 0, otherwise an exception occurs.
     * If the radius is not specified, the default value of 1.0
     * is used.
     * 
     * @param double $radius the programmer-specified radius
     */
    public function __construct($radius = 1.0) {
        
        // make sure radius is valid if it was provided
        if ($radius != DEFAULT_RADIUS) {
            $this->setRadius($radius);
        }
    }

    /**
     * Retrieves the radius of this circle object.
     * 
     * @return double the radius of this circle
     */
    public function getRadius() {
        return $this->radius;
    }

    /**
     * Places a valid programmer-specified radius into this
     * circle object.  If the radius provided is 0 or less,
     * an Exception occurs.
     * 
     * @param double $radius the programmer-specified radius
     * @throws InvalidArgumentException if the radius is invalid
     */
    public function setRadius($radius) {
        
        // make sure the provided radius is valid
        if ($radius <= 0) {
            throw new InvalidArgumentException("Radius must be greater than 0.");
        } else {
            $this->radius = $radius;
        }
    }

    /**
     * Creates a string representation of this circle object,
     * which includes this circle's radius.
     * 
     * @return string this circle as a string
     */
    public function __toString() {
        
        // formatted circle object
        return __CLASS__." radius=".$this->radius;
    }
}
?>

Using Classes

In your index file of your current project, add some code that uses the Circle class:

Catching Exceptions

If a method throws an exception, you might wish to catch it so that the error output doesn't appear on the user's screen. You can use a try-catch block just like you would in Java. The exception parameter also has the same getMessage() method you're already familiar with in Java exceptions:

<?php
    try {
        $circle->setRadius(-1);
    } catch (Exception $ex) {
        echo $ex->getMessage();
    }

Exercises

1. Add 2 new methods to your Circle class:

Add statements to your index file that displays the area and circumference of the 2 circles.

Add statements that change the radius of your circle to an invalid value. Use a try-catch block to display the exception's error message. Why do you see the output that appears? Where does that output come from?

2. Create a PHP class called User that models a user that might wish to log into your application with specific credentials:

Class: User
- username : String
- password : String
- email : String
+ User(name : String, password : String, email : String)
+ getUsername() : String
+ setUsername(name : String) : void
+ getPassword() : String
+ setPassword(password : String) : void
+ getEmail() : String
+ setEmail(email : String) : void
+ toString() : String

Your User class must be fully documented using PHPDoc.

Your mutator methods must construct exception objects with friendly error messages that follow standards for informative error messages (e.g. don't refer to how the data is input such as "Enter a valid...", be specific about what's wrong and how to fix it, etc).