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:
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.
private properties are only visible/accessible
inside the class.
public properties are available anywhere outside the class.
protected properties are only visible/accessible inside the
class and by the class's child classes.
Adding final to a property ensures that a child class can't
override the member, and it's also used for constants.
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:
Construct a circle with no radius and display the circle.
Construct a circle with a radius of 2.5 and display the circle.
Change the radius of the first circle to 5.2 and display the circle.
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:
calcCircumference: returns the circumference
of the circle as 2π * radius.
calcArea: returns the area of the circle
as π * radius2
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:
Your User class must be fully documented using PHPDoc.
username contains the user's chosen
user name. Username can't be an empty object or string and must be
1 to 15 characters in length. The username is only allowed
to contain digits and letters (Tip: see
ctype_alnum() method).
There is no default value.
password contains the hash
for the user's password. A user's password input must be
at least 12 characters. If the password is valid, convert
the password parameter string into a password hash
(Tip: see the
password_hash() function, I used
PASSWORD_BCRYPT). There is no default.
email is the user's email.
The email must be a valid email (e.g. FILTER_VALIDATE_EMAIL
or you can use a regex). There is no default.
The toString() should return the user name followed by the
email address e.g. "fred123: fred@flintstone.com"
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).