开发者

How to structure several PHP classes

I'm wondering if anyone could give me a suggestion for to best handle this situation:

I have several systems from which to pull data to display on a single PHP-driven website. The type of information will be the same across systems (contacts, addresses, etc) but the way I pull data (MS-SQL, XML, REST) will not.

I want to create a class, or set of classes, for each of the connection types and use simple methods such as getC开发者_如何转开发ontact(), getAddress(), etc. I am wondering how best to structure this.

The most obvious way that comes to mind means creating classes for each connection type, like:

class.sys_mysql.php. class.sys_xml.php, etc

But then won't I be duplicating the methods in each class? Maybe that's OK, but I'm curious if there's a better way, as far as future maintenance goes.

Maybe I should simply isolate the queries/data extraction methods, into separate class files? Classes within classes? Extended classes? I'm less familiar with these.

Any advice would be greatly appreciated.

DC

--------- more info ----------

Hi all. I really appreciate all the great advice. Not to belabor this thread but I'm still a bit confused on how I should break things down. I will try and be a bit more specific:

Basically, I have 3 (more in the future) offices, from which one PHP website pulls information. Each office uses a different CRM, and a different system for interfacing with that CRM. One uses MSSQL, another XML requests, etc.

Each office wants to display information similarly on the website, but there are minor differences. There may be more differences in the future. However, there are by far more similarities, and so I want to capitalize on higher level functions like getContacts($id) which are shared between them.

I am trying to write these classes so I can:

1) use higher level methods to pull data easily

2) account for different ways of pulling data (xml,sql,etc)

3) account for differences between how data is displayed on the website (office 1, office 2, office 3)

4) manage the connection credentials for each office and allow for expandability_

5) I should also mention that I will be creating separate classes for reporting, sending out automated e-mails, calculating finances...separate modules that will need to use existing classes to pull data.

I realize that some of the examples here see to cover 1 and 2, but I am confused as to how to get 3, 4 and 5 working with 1 and 2.

I really appreciate the help.

DC


This is what Interfaces are for.

You define the methods required to interact with the data in an Interface, and then you create classes that implement that Interface

If some of the systems have similar access models (i.e. perhaps two different DB Servers, but both are accessed using PDO) you could abstract it further and put the "low level" functionality into service-specific classes (which implement an Interface) and then a higher-level class which defines the actual methods you use.

Another option is that you could put the "common" methods (those that are identical or can be made idetntical with service-type checks) into a base class, which all others extend.

Example for option one:

interface DataModel {
    public function findContacts($search);
    public function getContact($id);
    public function findAddresses($search);
    public function getAddress($id);
}

class XMLDataModel implements DataModel {
    public function findContacts($search) {
        ...
    }

    public function getContact($id) {
        ...
    }

    public function findAddresses($search) {
        ...
    }

    public function getAddress($id) {
        ...
    }
}

class RESTDataModel implements DataModel {
    public function findContacts($search) {
        ...
    }

    public function getContact($id) {
        ...
    }

    public function findAddresses($search) {
        ...
    }

    public function getAddress($id) {
        ...
    }
}

As you can see, you simply define an Interface, which specifies which methods a class must implement.

If you had two very similar classes, perhaps one for MySQL and one for PostreSQL, and you can't/don't want to combine them into a single PDO class, you could do the following:

class PDODataModel implements DataModel {

    private $model;

    public function __construct ($serverType) {
        if ($serverType === 'mysql') {
            $this->model = new MySQLPDODataModel();
        }
        elseif ($serverType === 'postgresql') {
            $this->model = new PostgresQLPDODataModel();
        }
    }

    public function findContacts($search) {
        // common logic about $search, perhaps checking it's a valid search?

        $result = $this->model->searchForContacts($search);

        // more common logic, maybe higher level filtering..

        return $result;
    }

    public function getContact($id) {
        ...
    }

    public function findAddresses($search) {
        ...
    }

    public function getAddress($id) {
        ...
    }
}

interface PDODataModelDriver {
    public function searchForContacts($search);
}

class MySQLPDODataModel extends PDODataModel implements PDODataModelDriver {

    public function searchForContacts($search) {

        // MySQL-specific query to search for contacts
    }    

}

class PostgresSQLPDODataModel extends PDODataModel implements PDODataModelDriver {

    public function searchForContacts($search) {

        // PostgreSQL-specific query to search for contacts
    }    

}

The other option I mentioned was to work in the opposite direction:

abstract class PDODataModel implements DataModel {

    protected $pdo;
    protected $dsn;

    public function __construct () {
        $this->pdo = new PDO($this->dsn);
    }

    public function findContacts($search) {
        // common logic about $search, perhaps checking it's a valid search?

        $result = $this->searchForContacts($search);

        // more common logic, maybe higher level filtering..

        return $result;
    }

    public function getContact($id) {
        ...
    }

    public function findAddresses($search) {
        ...
    }

    public function getAddress($id) {
        ...
    }
}

class MySQLPDODataModel extends PDODataModel {

    protected $dsn = 'mysql:dbname=testdb;host=127.0.0.1';

    protected function searchForContacts($search) {

        // MySQL-specific query to search for contacts
    }    

}

class PostgresSQLPDODataModel extends PDODataModel {

    protected $dsn = 'pgsql:host=localhost;port=5432;dbname=testdb';

    protected function searchForContacts($search) {

        // PostgreSQL-specific query to search for contacts
    }    

}


This is a classical example of a strategy design patter. Your first mind was absolutely fine, but if you're repeating yourself in each class you should consider creation of a abstract class that will handle the common code.

So it could look like this:

$myService = new MyService(new XMLReader('/path/to/file'));

echo $myService->getContanct('abc')->getName();

And skeleton of your classes:

 class MyService {
      private $reader;

      public function __construct(ReaderInterface $reader) {
          $this->reader = $reader;
      }

      // ...

      public function getContacnt($id) {
          $contact =  $this->reader->getContact($id);

          // do some extra stuff here

          return $contact;
      }
 }

 interface ReaderInterface {
      public function getContanct($id);
      public function getAddress($id);
 }

 abstract class AbstractReader implements ReaderInterface {
     protected $loaded = false;
     protected $data = array();

     abstract protected function load();

     public function getContanct($id) {
         if ($this->loaded == false) {
             $this->load();
             $this->loaded = true;
         }

         return $this->data['contact'][$id];
     }
 }

 class XMLReader extends AbstractReader {
      public function __construct($filepath) {
          ...
      }

      protected function load() {
          ...
          foreach (...) {
              $this->data[...] = ...;
          }
      }
 }

 class MSSQLReader extends AbstractReader {
      public function __construct(PDO $dbh) {
          ...
      }

      protected function load() {
          ...
          while ($row = $stmt->fetchRow()) {
              $this->data[...] = ...;
          }
      }
 }

EDIT (2011-03-07) - According to your comment.

  1. PHP supports variable variables (new $type()) but never use this! It's a horrible, and if overused make code really crappy.
  2. This is a yet another example of a "classical issue". Use a factory pattern (depending on the complexion of the creation you might want to use more abstract variety of this pattern - abstract factory
  3. When you need to dynamically determine class name (eg. from variable) use reflection API to instate an object.


You should create an object-storage mapping layer for each data source, which instantiates the objects into storage agnostic model objects. See http://martinfowler.com/eaaCatalog/dataMapper.html


If you have control over the structure of your data formats, I suggest you serialize your data in a consistent way (especially in XML) and provide drivers for each data format.

For instance, every driver will have 'findAll', 'getOne', 'count', etc. methods. The driver can be given a model to populate with the retrieved data.

abstract class DataDriver {
    function __construct($model) {}
    abstract public function findAll();
    abstract public function getOne();
    abstract public function count();
    // ...
}
class XMLDriver extends DataDriver {
    // implements all the methods
}
class SQLDriver extends DataDriver {
    // implements all the methods
}

class Contact {
    public var $firstName;
    public var $lastName;

    function getFullName() {
        return trim($this->firstName . ' ' . $this->lastName);
    }
}

$accessor = new SQLDriver('Contact');
$contacts = $accessor->findAll();

If your data will be serialized in an uncontrolled manner, the approach you suggest is the best. Just make sure to separate your models (e.g. Address book, Contact) from the method of retrieval (eg. get_address_book_xml, get_address_book_sql, etc.)

Of course there are many ways of separating your models from your data-mapping driver. The importance is you find the solution that works best for you given that you're using such different formats.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜