Приглашаем посетить
Чернышевский (chernyshevskiy.lit-info.ru)

Object-Oriented Programming

Previous
Table of Contents
Next

Object-Oriented Programming

One of the key features of object-oriented programming (OOP) is the ability to create new data types in which the data and the implementation of operations are bound together. You create classes that contain the data representing the properties of a particular item, as well as the set of operations that can be performed on it. In addition, most languagesincluding PHPprovide mechanisms to customize and extend your objects so that you can create new variations on the types and share common code within them.

Some Basic Terminology

In object-oriented programming, you write code to define new data types, or classes, to represent abstract items. These classes contain properties that represent the fundamental information needed to describe an item. Along with these properties, we can associate operations, called methods or member functions, which you would normally want to perform on those items.

When you create an instance of a class, you have created an object. Therefore, for the products in Beth's Boating Supplies warehouse, we could define a class called Product. This class would have the properties we saw previously, in addition to two member functions or methods:

Product class:
  Properties:
    - product id
    - product name
    - description
    - price per unit
    - supplier location
  Methods:
    - get number in stock
    - add one to an order

An object instance of this class would be one of these Product classes instantiated with the information for a particular product:

Product instance for a fly-fishing lure:
  Properties:
    - product id -- 334355-XR1
    - product name  Joe's Awesome Winter Lure
    - description -- Joe's lures are amazingly popular with...
    - price per unit -- 19.99
    - supplier location -- local warehouse
  Methods:
    - get number in stock
    - add one to an order

The Basics of Objects in PHP

We declare classes in PHP by using the class keyword. The contents of the class (the data members and implementation methods) are enclosed between brackets.

<?php

  class Product
  {
    // contents, including properties and methods, go here
  }

?>

For the previous example, we said that the properties are the product identification number (PID), product name, description, price, and location. In PHP, you declare these properties in your class as follows:

<?php

  class Product
  {
    public $id;
    public $name;
    public $desc;
    public $price_per_unit;
    public $location;
  }

?>

The class Product now has five properties, or member variables, that represent its fundamental information. Like all variables in PHP, they have no declared type associated with them. What is new about the code we have written, however, is that we have added the keyword public in front of each line. This keyword tells PHP that everybody is allowed to inspect and modify the data that we have assigned to this object. We will learn more about this and similar keywords later in the section "Visibility: Controlling Who Sees Things."

When you declare a member variable in PHP, you can optionally provide it with a default or "initial" value by assignment:

<?php

  class Product
  {
    public $id;
    public $name = "Unknown Product";
    public desc;
    public $price_per_unit;
    public $location = LOCAL_PRODUCT;
  }

?>

Each new instance of the class has the $name and $location member variables assigned to those default values. The other member variables are unset until a value is assigned to them.

In order to create an object instance, you use the new keyword along with the name of the class you wish to instantiate:

<?php

  $prod = new Product();

?>

As we learned in Chapter 2, you can access a member variable on an instance of the class by using the -> operator.

<?php

  echo "The product's name is: {$prod->name}<br/>\n";

?>

Similarly, you can set the value of a member variable with the same operator:

<?php

  $prod->name = "StormMeister 3000 Weather Radar";

?>

To add a member function to your class, simply put the function inside of the class declaration alongside the member variables. The function now has a visibility level:

<?php

  class Product
  {
    public $id;
    public $name;
    public $desc;
    public $price_per_unit;
    public $location;

    public function get_number_in_stock()
    {
       if ($this->location == LOCAL_PRODUCT)
       {
         // go to local database and find out how many we
         // have, returning this number, 0 if none.
       }
       else if ($this->location == NAVIGATION_PARTNER_PRODUCT)
       {
         // communicate with our navigation partner's systems
         // and ask how many are left.
       }
       else
       {
         // this is an error -- we'll talk about how to deal with
         // this more elegantly later.
         return -1;
       }
     }
   }

?>

Like regular functions in PHP, you cannot declare more than one member function with the same name within a class.

To access a member variable from within a member function, you must use the special variable (also called a pseudo-variable) $this. It refers to the current instance of the object itself. You combine it with the -> operator to access the member variables.

if ($this->price_per_unit > 100)
{
  echo "Phew! I'm expensive";
}
else
{
  echo "I'm affordable";
}

To call a member function given a reference to an object instance, you use the -> operator:

<?php

  $prod = get_most_popular_product();

  $num_stocked = $prod->get_number_in_stock();

?>

This example shows us more clearly that the method get_number_in_stock is not only operating on a broad class of products, but also on a specific instance of a product. We are now working in a system where the inherent properties and capabilities of items are what we use. We can now consider our program execution to be something like that in Figure 4-2.

Figure 4-2. Object-oriented classes have both data and implementation.

Object-Oriented Programming


To call a member function from within one of our classes, we must again prefix the reference with $this: pseudo variable, as follows:

<?php

  class Product
  {
    // etc....

    public function do_i_have_any()
    {
      if ($this->get_number_in_stock() > 0)
        return "YES";
      else
        return "NOPE";
    }
  }

?>

Once we have created and used an object in PHP, we can get rid of it by simply removing all references to it. The PHP language engine figures out it is no longer in use and cleans it up.

<?php

  $prod1 = new Product();
  $prod2 = $prod1;         // both are pointing to the same obj
  $prod1 = NULL;           // object not cleaned up yet: $prod2
  $prod2 = NULL;           // no more references --
                           // object will be deleted
?>

PHP will not always delete the object immediately; it will only delete the object when it is sure that nobody is referencing it any longer. (All objects are eventually destroyed when script execution terminates.) If you want to make sure some code is executed when you are done with the object, you might want to consider adding a clean_up method or something similar to call when you are certain you are finished with it.

Initializing and Cleaning Up Objects

While we had the beginnings of a Product object type earlier, one thing about its usage that certainly seems suboptimal is the need to set the member data manually. To use it, we would have to write code along the following lines:

<?php

   $prod = new Product();
   $prod->name = "Super Fishing Pole 10b";
   $prod->desc = "This is the best fishing pole ever ...";
   // etc.

?>

What we would ideally like is a system in which we can pass the data about the product to the Product object as we create it so that from the moment of its creation, the object knows its properties and can verify that it has everything it needs.

To solve this problem, we introduce the concept of a constructor. This is a method that is executed whenever a new instance of the object type is created. The constructor can accept parameters that are passed via the call to the new operator by putting them in parentheses after the type name. If you do not write your own constructor, a default or blank one is provided for you that accepts no parameters. (This explains why the preceding sample code has empty parentheses after the name Product.)

<?php

  $prod = new Product();  // default constructor has no args.

?>

We will now define our constructor for the Product class. This constructor will require programmers to give us the necessary information when creating an object for a product. To do this in PHP, you define a method with the name __construct and make sure the keyword function is placed before it:

<?

  class Product
  {
    // member variables, etc....

    public function __construct
    (
      $in_prodid,
      $in_prodname,
      $in_proddesc,
      $in_price_pu,
      $in_location
    )
    {
      $this->id = $in_prodid;
      $this->name = $in_prodname;
      $this->desc = $in_proddesc;
      $this->price_per_unit = $in_price_pu;
      $this->location = $in_location;
    }

    // more methods and stuff follows
  }

?>

Now, when you create an instance of this class, you are obliged to pass in the required data:

<?php

  $prod = new Product($product_id,
                      $product_name, 
                      $product_description,
                      $product_unit_price,
                      $product_supplier);

?>

If somebody tries to create an instance of the Product class without passing in the appropriate values to the constructor, PHP triggers a warning for each missing parameter.

Similarly, there exists a special method called a destructor that is called whenever our object is finally destroyed. We implement this in PHP via a function called __destruct. If we do not define one, PHP does its own cleanup and continues. For our Product class, there is not much cleanup required, but we can still put in a __destruct method just in case.

<?php 

  class Product
  {
    // member declarations, constructors, etc.

    public function __destruct()
    {
      // put cleanup code here!!
    }
  }

?>

As mentioned earlier, the destructor for an object instance is not always called when you would expect. If PHP can easily determine there are no outstanding references left to an object, it immediately destroys the object and calls your destructor. However, in other situations, it may take until the end of the script to figure out that there are no references to an object that can be destroyed. If you are uncertain as to when your object will be destroyed, implement a destroy or clean_up method to call when you finish using an object.

Visibility: Controlling Who Sees Things

One major problem with our Product class is that our member data is publically readable and writable, as indicated by the keyword public in front of the five member variables. What we would really like to find is a way for the object to store the data in such a way that only it can modify the data and otherwise provide some other way for external authors to query their values.

To do this, PHP provides visibility keywords, or keywords that you use to control who can view and modify member variables as well as call member functions. Three visibility modifiers are provided:

  • public Anybody may read and modify this member variable or call this method. This modifier can be used by external code, from code within the given class, or in classes that extend the functionality of the given class (more on this later in this chapter).

  • private Only code within this class may read and modify private variables or call private member functions. No other codes, classes, or classes that extend the functionality of this class may use private items.

  • protected External code and other classes that have no relation to the given class may neither read or modify member variables with the protected keyword, nor call protected member functions. However, classes that extend the functionality of this class are allowed to access or call it.

Therefore, for our Product class, if we did not want people to modify the name, PID, or description of a product, we could change the visibility modifier from public to either private or protected. (We will use protected to allow ourselves to extend our class later on.) We have shot ourselves in the foot, however, since external code is now unable to query the values of our member data and learn about the product. ("We have a million products in our database, and we are not going to tell you about any of them!")

To solve this problem, we can create functions to get the values of the member data we wish to expose to people. Our Product class will now start to look as follows:

<?php

  class Product
  {
    protected $id;
    protected $name;
    protected $desc;
    protected $price_per_unit;
    protected $location;

    public function __construct
    (
      $in_prodid,
      $in_prodname,
      $in_proddesc,
      $in_price_pu,
      $in_location
    )
    {
      $this->id = $in_prodid;
      $this->name = $in_prodname;
      $this->desc = $in_proddesc;
      $this->price_per_unit = $in_price_pu;
      $this->location = $in_location;
    }

    public function __destruct()
    {
    }

    public function get_number_in_stock($in_num_desired)
    {
      // details omitted
    }

    public function ship_product_units($in_num_shipped)
    {
      // details omitted
    }

    public function get_ProductID()
    {
      return $this->id;
    }

    public function get_Name()
    {
      return $this->name;
    }

    public function get_Description()
    {
      return $this->desc;
    }

    public function get_PricePerUnit()
    {
      return $this->price_per_unit;
    }

    public function get_Location()
    {
      return $this->location;
    }
  }
?>

If we wanted to declare a number of helper functions or subroutines in our class that we could call only from within the implementation of other functions, we would declare them as being private or protected. For example, while implementing our get_number_in_stock_method, we might want to call other member functions, depending on where the products are received:

<?php

  class Product
  {
    // members, constructors, other methods

    public function get_number_in_stock($in_num_desired)
    {
      if ($this->location == LOCAL_PRODUCT)
      {
        return $this->check_local_product_inv($in_num_desired);
      }
      else
      {
        return $this->check_nav_partner_inv($in_num_desired);
      }
    }

    private function check_local_product_inv($in_num_desired)
    {
      // go to local databases and see how many we have
    }

    private function check_nav_partner_inv($in_num_desired)
    {
      // go to navigation equipment partner servers and see
      // how many they claim to have left ...
    }

    // etc...
  }

?>

Adding Static Data to Classes

The primary use of classes is to let us bind implementation and data into objects. However, situations arise where we want the ability to expose information about that class of objects without binding it to a particular object instance.

Class Constants

For example, in our preceding Product class, we are using two external constants (LOCAL_PRODUCT and NAVIGATION_PARTNER_PRODUCT) to represent possible sources for products. These pieces of information are directly related to a product, but they are not tied to a particular instance. We would like a way to associate these constants, and any new ones we defined to represent new suppliers or partner locations, with our Products class.

To solve this, PHP allows us to define public constants, which is done with the const keyword.

<?php

  class Product
  {
    const LOCAL_PRODUCT = 1;
    const NAVIGATION_PARTNER_PRODUCT = 2;

    // etc....
  }

?>

Constants are publically available, and can be used by anyone. They are not associated with any particular instance of this classthey are pieces of information associated with the class of objects or typeand you therefore cannot use the dereferencing operator (->) to access them. There are two contexts in which these constants can be used:

  • From outside the class To access a class constant outside of the class in which it is defined, you must first list the class name, and then use the scope resolution operator, which consists of two colons (::, also referred to as Paamayim Nekudotayim, which is Hebrew for "two colons"). Finally, you would use the constant name. Now, for our preceding example, we might see the creation of a new instance of the Product class as

    <?php
       $prod = new Product("101-44A55c",
                           "10 gallon gas tank",
                           "The 10 gallon gas tank is a ...",
                           14.95,
                           Product::LOCAL_PRODUCT);
    ?>
    

  • From within the class You can refer to the class name as in the previous code (in this case, Product), or you can use the new keyword, self, to tell PHP to look in the current class (or any of the classes whose functionality it extends) for such a constant. You can then use the scope resolution operator (::) and the constant name.

    <?php
      class Product
      {
         // etc.
    
        public function get_number_in_stock
        (
          $in_num_desired
        )
        {
          if ($this->location == self::LOCAL_PRODUCT)
          {
            // etc.
          }
          else
          {
            // etc.
          }
        }
      }
    ?>
    

Static Member Variables

For situations where you would like to associate some data with an entire class of objects, but the data is not fixed (in other words, constant), you can create member variables that are global to the entire class, not just specific instances. In PHP, these are called static member variables. They are declared by prefixing their declarations with the keyword static.

<?php

  class ABC
  {
    public static $x;

    // etc.
  }

?>

You would declare static member variables just like any other; you can specify a visibility modifier with it and assign it an initial value. For the previous example, class ABC, we have declared a static class variable called x that anybody can access and modify. Any instance of class ABC sees the same value if it asks for the value of x, and it does not have its own storage location for it.

Accessing the value of a static variable is similar to accessing for a class constant: You either use the type name (outside of or within the class) or the keyword self, both followed by the scope resolution operator (::) and the name of the static variable, starting with $. As a more interesting example, we can use a static member variable to count the number of instances of a class (such as our Product class) that have been created:

<?php

  class Product
  {
    private static $num_instances_created = 0;

    public function __construct
    (
      $in_prodid,
      $in_prodname,
      $in_proddesc,
      $in_price_pu,
      $in_location
    )
    {
      $this->id = $in_prodid;
      $this->name = $in_prodname;
      $this->desc = $in_proddesc;
      $this->price_per_unit = $in_price_pu;
      $this->location = $in_location;

      self::$num_instances_created++;
      echo "I've created " . self::$num_instances_created 
           . " instances so far!<br/>\n";
    }
   }

?>

Unlike constants, static member variables can be declared at a scoping level other than public, and they are not read-only.

As we mentioned in the section "Variable Lifetime" in Chapter 2, PHP remembers nothing between script invocations, including variable values. Static class variables are no exceptiontheir value is reset every time script execution restarts anew. The following script always prints out 10:

<?php

class ABC
{
  public static $value = 10;
}

ABC::$value++;
echo ABC::$value;
}

?>

Static Methods

There will also come a time when you want to associate methods with your type that do not necessarily operate on a specific instance but are broad operations related to that type. For example, we might want to define a method on the Product class that creates a number of Product objects. This would let us put most of the implementation of products into one class!

We declare a static method by declaring a normal function with the static keyword included:

<?php

  class Product
  {
    // etc....

    public static function get_matching_products($in_keyword)
    {
      // go to the db and get all the products matching
      // the keyword given ... this would probably return
      // an array of Product objects.
    }
  }

?>

We have now set up our Product class so that various pages in our web application can call a method to get an array of Product objects as follows:

<?php

  $prods = Product::get_matching_products($keyword);

?>

Static methods are allowed to have a visibility modifier of private or protected to restrict access to information.

:: vs. ->, self vs. $this

For people confused about the difference between :: and -> or self and $this, we present the following rules:

  • If the variable or method being referenced is declared as const or static, then you must use the :: operator.

  • If the variable or method being referenced is not declared as const or static, then you must use the -> operator.

  • If you are accessing a const or static variable or method from within a class, then you must use the self-reference self.

  • If you are accessing a variable or method from within a class that is not const or static, then you must use the self-referencing variable $this.


Previous
Table of Contents
Next