简体   繁体   中英

Create database model classes dynamically

I am trying to improve the method that I am using to to database transactions in a light framework I've built.

Information to understand the question:

Here's a class I've written (where connect.php loads up database credentials; a wrapper for the PHP PDO, stored in $db ; and Base.php ):

<?php
    require_once('connect.php');

    class Advertiser extends Base
    {
        public static function getByID($id)
        {
            global $db;
            $sql = "SELECT * FROM advertiser WHERE advertiserid=?";
            $values = array($id);
            $res = $db->qwv($sql, $values);

            return Advertiser::wrap($res);
        }

        public static function add($name)
        {
            $adv = new Advertiser(null, $name);
            $res = $adv->save();

            return $res;
        }

        public static function wrap($advs)
        {
            $advList = array();
            foreach( $advs as $adv )
            {
                array_push($advList, new Advertiser($adv['advertiserid'], $adv['name']));
            }

            return Advertiser::sendback($advList);
        }

        private $advertiserid;
        private $name;

        public function __construct($advertiserid, $name)
        {
            $this->advertiserid = $advertiserid;
            $this->name = $name;
        }

        public function __get($var)
        {
            return $this->$var;         
        }

        public function save()
        {
            global $db;
            if( !isset($this->advertiserid) )
            {
                $sql = "INSERT INTO advertisers (name) VALUES(?)";
                $values = array($this->name);
                $db->qwv($sql, $values);

                if( $db->stat() )
                {
                    $this->advertiserid = $db->last();
                    return $this;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                $sql = "UPDATE advertisers SET name=? WHERE advertiserid=?";
                $values = array ($this->name, $this->advertiserid);
                $db->qwv($sql, $values);

                return $db->stat();
            }
        }
    }
?>

As you can see, it has fairly standard CRUD functions ( Edit : Okay, so only CRU, in this implementation). Sometimes, I'll extend a class like this by adding more functions, which is what these classes are intended for. For example, I might add the following function to this class (assuming I add a column isBanned to the database):

public static function getBanned()
{
    global $db;
    $sql = "SELECT * FROM advertiser WHERE isBanned=1";
    $res = $db->q($sql);

    return Advertiser::wrap($res);
}

The question:

How can I create a catchall class that will also load up custom model classes when present and necessary?

For example, if I write the following code:

$model = new Catchall();
$banned = $model->Advertiser::getByID(4);

I would expect my catchall class to modify its queries so that all the references to the tables/columns are whatever name I chose ( Advertiser , in this case), but in lower case.

In addition, if I wanted to create a custom function like the one I wrote above, I would expect my catchall class to determine that a file exists in its path (previously defined, of course) with the name that I've specified ( Advertisers.php , in this case) and load it.

Advertisers.php would extends Catchall and would contain only my custom function.

In this way, I could have a single Catchall class that would work for all CRUD functions, and be able to easily expand arbitrary classes as necessary.

  1. What are the ideas / concepts that I need to understand to do this?
  2. Where can I find examples of this already in the wild, without digging through a lot of CodeIgniter or Zend sourcecode ?
  3. What is what I'm trying to do called ?

General Stuff : I would look into Doctrine2 for examples of how they make an ORM in PHP. They use mapping in a markup language to say: this table has these columns of this type. Also, while not in PHP, the Django ORM is very easy to use and understand, and working through that tutorial for 20 minutes or so will really open your eyes to some neat possibilities. (it did for me)

A quick search for "php active record lightweight" returned several interesting examples that might start you down the right path.

PHP Ideas : I would look into the magic getter and setter in php, __GET and __SET that will let you set values on your objects without having to make a getter/setter for each field of each table. You could make a single __SET that will make sure that set field is a field in that table, and add it to the list of "fields to update" next time that object is saved. BUT, this is not really a good idea long term, as it gets out of hand quickly, and is brittle.

Advice : Lastly, I worked at a company that used a system that looks almost exactly like this, and I can say unequivocally, you do not want to try to scale this long term. A system like this (the active record pattern) can save massive amounts of time up front, by not having to write queries and things, but can cost tons down the road, if you ever want to start unit testing business logic on the object classes.

For example, it is not possible to mock/dependency inject that static GetById method (it is basically a global method), so every time that is called in code, the code will go to the real database and return a real object. It doesn't take much coding like this to make a system that is almost impossible to test, snarled and tightly coupled to the database.

While they can perform a little slower than your code above, if you are planning on having this around for a considerable amount of time, try looking into ORM tools.

Edit It's called Active Record.

There are a couple different design patterns for what you are trying to do. Look into Data Mapper and Active Record .

Using PHP's "magic method" __get , you can produce this functionality, when you access it via :

$model = new Catchall();
$banned = $model->Advertiser->getByID(4);

... it will a) check to see if the class Advertiser is already defined, b) check for a file called Advertiser.php and include it, or c) return a new instance of a generic class.

The syntax you used in your example with :: assumes that the returned class is static. I have not written this code to contend with that, but it should be trivial to do so.

See the docs: http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods

Quick and dirty example:

    public function __get($name) {

            $instance = false;
            // see if there is already a class with the requested name
            if (class_exists($name)) {
                $instance = new $name();
            }

            // check to see if there is an object def for the requested object
            if ($instance === false && file_exists(PATH_TO_OBJECTS.$name.'.php')) {
                require_once(PATH_TO_OBJECTS.$name.'.php');
                $instance = new $name();
            }

            // if instace is still not found, load up a generic
            if ($instance === false)
                $instance = new Catchall($name);

        return $instance;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM