Question

I have done a few projects lately using a Database Object super class which I use for quick one off record query/update, and extending with appropriate classes, such as a User class.

I found that many classes I was writing had the exact same methods: query_values(), update(), delete(), etc.

So I came up with a class with a constructor that looks like this:

public function __construct($table, $db_object, $record_id = null){
    $this->db = $db_object; // Database object with query methods

    $this->table = $table; // The name of the database table

    $this->get_column_data();

    if(!is_null($record_id)){
        // This retrieves all column values, 
        // stores into private $fields array property
        $this->query_values($record_id);
    }
}

And a child class constructor looks like this:

public function __construct($db_object, $record_id = null){
    parent::__construct($this->table, $db_object, $record_id);
}

Where the $table property is defined at the top, as we should know which table this specific object works with.

Now, all common record management methods are in one place, and methods specific to the class are all that is defined in their respective child-classes.

The biggest drawback I see here is that all data fields are pulled and are encapsulated within the $fields property, so either generic get and set methods need to be defined (which I typically do) which almost negates the encapsulation*, or a method must be defined specifically for each property we want to expose.

*Example: $user_id = $User->id; // NOT USING MY METHOD vs. $user_id = $User->_get('id'); // ACCESSES $User->fields['id']

Do you see this as a drawback, or a plus? The goal being ease of use, object-orientation (encapsulation), and just being plain awesome!

Was it helpful?

Solution

Well, you could make your life easy and use PHP's magic overloading __call method to create generic getters and setters. You could add the following method to your "Database Object super-class":

/**
 * Create magic getter and setter methods to access private $fields array 
 */
public function __call($method, $args)
{
  $prefix = substr($method, 0, 3);
  $prop   = lcfirst(substr($method, 3));

  if (isset($this->fields[$prop])) {

    if ($prefix == 'get') {
      return $this->fields[$prop];
    } elseif ($prefix == 'set') {
      if ( ! isset($args[0])) {
        $msg = 'Missing argument: ' . get_class($this) . "::$method must specify a value";
        throw new InvalidArgumentException($msg);
      }
      $this->fields[$prop] = $args[0];
      return;
    }
  }

  $msg = 'Invalid method: ' . get_class($this) . "::$method does not exist";
  throw new BadMethodCallException($msg);
}

So let me explain what's going on here. The magic __call method will receive calls to any object method that does not match one of the object's concrete methods. It receives as parameters the name of the method that was called and an array of its arguments.

The __call method above does a quick substr check to see if the method was a "getter" or "setter" (using the first three letters of the method name). It expects that your $fields array stores lower-case "property" names (lcfirst) and uses everything after the setter/getter prefix as the expected property name.

If a property matches the getter method then that property it is returned. If not, the SplException BadMethodCallException is thrown. This is a best practice since the inclusion of the Spl exceptions in PHP.

Likewise a setter method will also throw the SplException InvalidArgumentException if no argument was specified.

PHP's magic methods will change your life. You can also use __get and __set to assign $fields array values in a similar fashion without making faux method calls. Get excited :)

OTHER TIPS

There are two major approaches:

PHP file creation

All big ORM / database projects I know do not let the user write all getter methods himself. Both Doctrine and Propel use automatical generators to create real PHP files with the getter methods when you call a command line script. These files contain so called Base methods which are automatically extended by some classes you will put your own code in (so recreation of the base class will not delete your own code).

Advantage of file creation is that you have a well defined interface (some people dislike it when callable methods are not visible directly within the source code).

Magic methods / overloading

A database engine I have seen at client’s application used magic methods to simulate getters and setters. You could either use magic methods __get() and __set() or __call() (called “overloading” in PHP documentation) for this. Which method to use depends on how you want to access your values (most common $obj->property or $obj->getProperty()).

Advantage of overloading is that you do not need to write a complex PHP code generator, nor do you need to call a command line script each time you change your database design.

You can have the best of both worlds by implementing a few magic methods on your objects. For example, by implementing __get, __set and __isset you can make the property accesses look "natural" (e.g. $user->id) while at the same time not having to define properties in each separate subclass.

For example, the implementation of __get could check at runtime the $fields member to see if the property you are attempting to get is valid for the specific object class. You can write this generic implementation once in the base class, and just populate $fields accordingly to make it work with any kind of object. This is actually what all modern mapping tools do, including probably all of the PHP frameworks that you have heard of.

As an aside, I hope you are caching the table schema per-class inside get_column_data. It would be really inefficient to requery the database for the schema of the same table every time you construct an object of the corresponding class.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top