I have Yii application and two tables with same structure tbl and tbl_history:
Now want to create model so it will select table by parameter I send when calling model. For example:
MyModel::model('tbl')->find();
//and
MyModel::model('tbl_history')->find();
Find related article with solution in Yii forum. Made same changes and finally got this in MyModel:
private $tableName = 'tbl'; // <=default value
private static $_models=array();
private $_md;
public static function model($tableName = false, $className=__CLASS__)
{
if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
if(!$tableName)
return parent::model($className);
if(isset(self::$_models[$tableName.$className]))
return self::$_models[$tableName.$className];
else
{
$model=self::$_models[$tableName.$className]=new $className(null);
$model->tableName = $tableName;
$model->_md=new CActiveRecordMetaData($model);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
Now when I make:
echo MyModel::model('tbl_history')->tableName(); // Output: tbl_history
It returns right value, but:
MyModel::model('tbl_history')->find();
still returns value for tbl.
Added:
public function __construct($id=null,$scenario=null){
var_dump($id);
echo '<br/>';
parent::__construct($scenario);
}
and got:
string(tbl_history)
string(tbl_history)
NULL
It means Yii makes call to model from other place but don't know from where and how to prevent it.
Also It makes 2 calls to model, is it too bad for performance?
It looks like the CActiveRecord::getMetaData() method needs to be overridden to achieve what you are looking for.
<?php
class TestActiveRecord extends CActiveRecord
{
private $tableName = 'tbl'; // <=default value
private static $_models=array();
private $_md;
public function __construct($scenario='insert', $tableName = null)
{
if($this->tableName === 'tbl' && $tableName !== null)
$this->tableName = $tableName;
parent::__construct($scenario);
}
public static function model($tableName = false, $className=__CLASS__)
{
if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
if(!$tableName)
return parent::model($className);
if(isset(self::$_models[$tableName.$className]))
return self::$_models[$tableName.$className];
else
{
$model=self::$_models[$tableName.$className]=new $className(null);
$model->tableName = $tableName;
$model->_md=new CActiveRecordMetaData($model);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
public function tableName()
{
return $this->tableName;
}
/**
* Returns the meta-data for this AR
* #return CActiveRecordMetaData the meta for this AR class.
*/
public function getMetaData()
{
if($this->_md!==null)
return $this->_md;
else
return $this->_md=static::model($this->tableName())->_md;
}
public function refreshMetaData()
{
$finder=static::model($this->tableName());
$finder->_md=new CActiveRecordMetaData($finder);
if($this!==$finder)
$this->_md=$finder->_md;
}
}
Maybe it's easier to make MyModelHistory which extends MyModel and overrides only one method - tableName().
I recommend implementing single table inheritance. In order to do this you will need to combine your tables with a flag or type column that states whether or not this is a history record. I've pasted a few links at the bottom so you can see how this is implemented in Yii and listed some of the benefits below.
Benefits:
You won't need to duplicate code commonly used between the models
Changes to this table will only need to be executed once.
Changes to the parent model will only need to be made once.
Code becomes generally more maintainable and readable.
You seperate the code that belongs specifically to tbl and tbl_history
http://www.yiiframework.com/wiki/198/single-table-inheritance/
http://en.wikipedia.org/wiki/Single_Table_Inheritance
I created a solution for performing this exact action a couple of months ago. This is a completely dynamic solution, you just pass the table name like you are looking for to the model. That solution was originally designed to work with the same database structure across multiple databases, but it was trivial to adapt it to work in the same database. The documentation for that is here. I'd recommend reading over it as it has more details about CDynamicRecord
It's easy to adapt to work with multiple tables. You can download the adaptation as a gist from github.
Usage
1) Download the Gist and drop it into ext, save as CDynamicRecordSDB.php
2) Create Your model in Model, and setup up as follows:
Basically, you want to extend CDynamicRecord, and override your model() and tableName() so they are compliant with CDyanmicRecord.
<?php
Yii::import('ext.CDynamicRecordSDB');
class Test extends CDynamicRecordSDB
{
public static function model($dbConnectionString = 0, $className=__CLASS__)
{
return parent::model($dbConnectionString, $className);
}
public function tableName()
{
return $this->dbConnectionString;
}
[... Do everything else after this ...]
}
3) Setup your model as you normally would.
Usage
The usage is identical to CActiveRecord, and you can perform all actions. No surprises. Just a couple examples below.
$data = Test::model('tbl')->findAll();
$data2 = new Test('tbl');
$data2->findAll();
foreach ($data as $row)
print_r($row->attributes);
$data = Test::model('tbl_history')->findAll();
foreach ($data as $row)
print_r($row->attributes);
Limitations
The only limitation with doing this is you have to modify how relations work. IF you plan on accessing a related model (Bar), and you have no intention on calling Bar by itself. Then Bar should extend CActiveRecord, and in Foo you can define normal relations. Yii magically carries over the CDbConnectionString across the instances for you.
OTHERWISE, if you intend to access models in the same database, but also want to retain the ability to call them by themselves, then Bar should extend CDynamicModel, and Foo should have a getter defined as follows.
public function getBar()
{
return Bar::model($this->$dbConnectionString);
}
A small way but work for me for any number of table
public static $dynamic_table_name="main_table";
public static function setDynamicTable($param)
{
self::$dynamic_table_name=self::$dynamic_table_name.$param;
}
/**
* #return string the associated database table name
*/
public function tableName($param='')
{
self::setDynamicTable($param);
return self::$dynamic_table_name;
}
// to use it like
ModelName::model()->tableName('_one');
ModelName::model()->tableName('_two');
ModelName::model()->tableName('_three');
Related
In my PHP application I have a base class for all database objects, which is further extended by specific model classes. It goes on like this (simplified):
abstract class Model extends Collection {
(...)
function __construct(string $primary_key, string $value) {
$data = MysqlDB::instance() -> select(static::TABLE, implode(',', static::COLUMNS), "$primary_key=\"$value\"");
if (count($data) != 1)
throw new ModelException('Select condition uncertain');
parent::__construct($data[0]);
}
public static function getById(string $id) : ?Model {
try {
return new static('id', $id);
} catch (ModelException $e) {
return NULL;
}
}
(...)
}
The point is, I use the getById function on child classes to obtain the needed objects. However, since the return type of getById method is Model, PhpStorm can't figure out what methods the objects has and highlights the ones I use as an error. Consequently, no type-hinting available.
For instance, assuming that final class User extends Model and that class User has method sendNotification, this kind of code:
User::getById($id) -> sendNotification($param1, $param2, ...)
has sendNotification yellowed out as though it did not exist.
I know this isn't that much of an issue, but it's really irritating in terms of that I get distracted by constant redundant warnings and that I can't use type-hinting in such a usage case.
Try with actual PHPDoc for your getById() where you specify dynamic type (e.g. #return static or #return $this).
That's the most common way of providing such "this class and its' children" typehint.
Another possible option is kind of the same .. but using PHP 7.1 functionality (so no PHPDoc blocks).
Try using self as return type instead of Model. I mean here:
public static function getById(string $id) : ?Model {
use this instead:
public static function getById(string $id) : ?self {
But for this one you should use PhpStorm 2017.2 EAP build as 2017.1.x has issues with that (see https://stackoverflow.com/a/44806801/783119 as an example).
There are three ways to achieve this, none of them has anything to do with PHPStorm.
First overwrite the method in every child with the proper return type:
/**
* #return User
*/
public static function getById(string $id) : ?Model { return parent::getById($id); }
Second add all possible children to the return tag of the abstract class.
/**
* #return Model|User|...
*/
public static function getById(string $id) : ?Model { /* ... */ }
Third add a type hint just in place:
/** #var User $user */
$user = User::getById($id);
$user->sendNotification($param1, $param2, ...);
These are not nice and hard to maintain. But there is no "setting" in PHPStorm for that.
I've created this class that fetches all data of a post from database.
class Post {
private $id;
protected $conn;
public $data;
function __construct(\mysqli $conn) {
$this->conn = $conn;
}
public function getId() {
return $this->id;
}
public function getConnection() {
return $this->conn;
}
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
return $this->data;
}
}
public function setId($id) {
$this->id = (int)$id;
}
}
Along with post, I also need to fetch all data of the user who created the post. I've three ways to do this:
1) By calling the User class inside of the Post class.
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Initiating User class
$user = new User($this->getConnection());
$user->setUserId($this->data->user_id);
$this->data->user = $user->getUserInfo();
return $this->data;
}
}
2) By extending the Post class with the User class.
class Post extends User {
....
Then calling methods from the User class
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Calling methods from the User class
$this->setUserId($this->data->user_id);
$this->data->user = $this->getUserInfo();
return $this->data;
}
}
3) By creating a User trait and using it in Post class.
class Post {
use UserTrait;
....
Then calling methods from the User trait
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Calling methods from the User trait
$this->setUserId($this->data->user_id);
$this->data->user = $this->getUserInfo();
return $this->data;
}
}
Between these 3, which one is the best approach in terms of dependency injection, performance, and cleanliness of code?
This question might get downvoted to hell because you're requesting an opinion, but I'll throw in my two cents.
You're basically building your own basic ORM. In that ORM you're dealing with entities. Different entities should be handled by different classes (Models), and there should be a 1:1 equivalence between the underlying DB table and the Model.
There are relations between your entities. Yous Post entity has an author that should be an instance of the User entity. However, you wouldn't say that the relation between the post table and the user table means they could be represented by the same Model, or different flavors of a parent Model.
If you used a full ORM (Doctrine, Propel, RedbeanPHP) you'd see that a relation such as the Post author means that, when using methods like getById() (in your case getPost() ) the retrieved Post entity will proceed to instance every depending entity by default.
In your case, instead of having $this->data->user_id the ORM would offer you a nested $this->data->user object so you wouldn't have to deal with that by yourself.
So, if you consider it's too soon to use an ORM, the first approach is easier to mantain and migrate when the time comes.
Unrelated opinion
At this point, to get a post info you need to do
$post = new Post($conn);
$post->setId($postid);
$postdata = $post->getPost();
If you changed your constructor and added a getById() method such that
function __construct(\mysqli $conn) {
$this->conn = $conn;
return $this;
}
public function getById($id) {
$this->setId($id);
return $this->getPost();
}
you could instead retrieve the post data in one line
$postdata=(new Post($conn))->getById($postid);
It all depends on what you're going to keep doing it. With the extended class was better deal if you're going to use permanent methods of other classes. If you need only a part of another class, then just call the class and its method.
CAREFUL: An extended class behaves as a single class. Here can be a lot of collisions occur if you have the same names of methods and functions.
I think this depends on other aspects of your code. Personally I would just extend the class User as I don't think using traits is necessary in this particular case.
There is a useful article here on using Traits in PHP -
Using Traits in PHP 5.4
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 getContact(), 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.
PHP supports variable variables (new $type()) but never use this! It's a horrible, and if overused make code really crappy.
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
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.
So, lets say I have a record:
$record = new Record();
and lets say I assign some data to that record:
$record->setName("SomeBobJoePerson");
How do I get that into the database. Do I.....
A) Have the module do it.
class Record{
public function __construct(DatabaseConnection $database)
{
$this->database = $database;
}
public function setName($name)
{
$this->database->query("query stuff here");
$this->name = $name;
}
}
B) Run through the modules at the end of the script
class Record{
private $changed = false;
public function __construct(array $data=array())
{
$this->data = $data;
}
public function setName($name)
{
$this->data['name'] = $name;
$this->changed = true;
}
public function isChanged()
{
return $this->changed;
}
public function toArray()
{
return $this->array;
}
}
class Updater
{
public function update(array $records)
{
foreach($records as $record)
{
if($record->isChanged())
{
$this->updateRecord($record->toArray());
}
}
}
public function updateRecord(){ // updates stuff
}
}
A question you could ask yourslef is whether you want to reinvent the wheel or not. ORM layers like Propel or Doctrine already implement object to (R)DBMS mapping, so you might look at their implementation details.
Propel will use your second approach, they even keep flags on a field level to create just one update statement (which will keep database interaction at a minimum). You'll learn a lot if you study their source (or better yet, stop wasting your time and use their implementation - you won't regret it :p).
It depends on how you plan to implement... Doing all the writes at a single point (at the end of a request) is nice because it allows you to optimize your operations by consolidating queries where possible. But to do that you have to create something similar to a UnitOfWork to keep track of whats a delete/update/insert which can open a whole other can of worms.
On the other hand if you do it directly when you call the persistence method on the entity then you dont have to worry about that quite as much.
Both approaches though mean you have to have some way to make sure you always have the current data in your object but the work required to implementation that varies in complexity with he approach you choose.
Example A updates the database whenever setName is called. This function looks like a simple write accessor but it performs expensive actions when called (connecting to the database, executing a query, etc). These unintended site-effects make Example B far more appealing.
As a further example: Later on you might need a Validator class that examines a Record and ensures that the Record is in a valid state. But in order to examine the Record you must define it first by setting a name - so the Record will be persisted before you can validate it's state. Defining object state is not the same as persisting object state.
A data model approach might work better instead of a record-based approach. For instance:
class Model {
protected $_props= array();
public $changed= false;
static public $models= array();
function __set($name, $value) {
$this->changed= true;
$this->_props[$name]= $value;
}
function __construct() {
Model::$models[]= $this;
}
public function save() {
// Execute database query for saving the current Model
}
static public function update() {
foreach (Model::$models as $model) {
if ($model->changed) {
$model->save();
}
}
}
}
A model-based solution really shines when it comes to creating different Model types. For instance:
class Person extends Model {
public function save() {
// Execute person-specific write operations
}
}
class Doctor extends Person {
public function save() {
// Execute all Person write operations
parent::save();
// Save the extra bits that belong to a doctor
}
}
$person1= new Person();
$person->firstname= 'Jon';
$person->lastname= 'Skeet';
$doctor1= new Doctor();
$doctor1->firstname= 'House';
$doctor1->lastname= 'MD';
// Save all modified models
Model::update();
Though I rarely find use for these kind of mass update mechanisms. Write conditions are usually more specific.
I'm working on creating a domain layer in Zend Framework that is separate from the data access layer. The Data Access Layer is composed to two main objects, a Table Data Gateway and a Row Data Gateway. As per Bill Karwin's reply to this earlier question I now have the following code for my domain Person object:
class Model_Row_Person
{
protected $_gateway;
public function __construct(Zend_Db_Table_Row $gateway)
{
$this->_gateway = $gateway;
}
public function login($userName, $password)
{
}
public function setPassword($password)
{
}
}
However, this only works with an individual row. I also need to create a domain object that can represent the entire table and (presumably) can be used to iterate through all of the Person's in the table and return the appropriate type of person (admin, buyer, etc) object for use. Basically, I envision something like the following:
class Model_Table_Person implements SeekableIterator, Countable, ArrayAccess
{
protected $_gateway;
public function __construct(Model_DbTable_Person $gateway)
{
$this->_gateway = $gateway;
}
public function current()
{
$current = $this->_gateway->fetchRow($this->_pointer);
return $this->_getUser($current);
}
private function _getUser(Zend_Db_Table_Row $current)
{
switch($current->userType)
{
case 'admin':
return new Model_Row_Administrator($current);
break;
case 'associate':
return new Model_Row_Associate($current);
break;
}
}
}
Is this is good/bad way to handle this particular problem? What improvements or adjustments should I make to the overall design?
Thanks in advance for your comments and criticisms.
I had in mind that you would use the Domain Model class to completely hide the fact that you're using a database table for persistence. So passing a Table object or a Row object should be completely under the covers:
<?php
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
$db = Zend_Db::factory('mysqli', array('dbname'=>'test',
'username'=>'root', 'password'=>'xxxx'));
Zend_Db_Table_Abstract::setDefaultAdapter($db);
class Table_Person extends Zend_Db_Table_Abstract
{
protected $_name = 'person';
}
class Model_Person
{
/** #var Zend_Db_Table */
protected static $table = null;
/** #var Zend_Db_Table_Row */
protected $person;
public static function init() {
if (self::$table == null) {
self::$table = new Table_Person();
}
}
protected static function factory(Zend_Db_Table_Row $personRow) {
$personClass = 'Model_Person_' . ucfirst($personRow->person_type);
return new $personClass($personRow);
}
public static function get($id) {
self::init();
$personRow = self::$table->find($id)->current();
return self::factory($personRow);
}
public static function getCollection() {
self::init();
$personRowset = self::$table->fetchAll();
$personArray = array();
foreach ($personRowset as $person) {
$personArray[] = self::factory($person);
}
return $personArray;
}
// protected constructor can only be called from this class, e.g. factory()
protected function __construct(Zend_Db_Table_Row $personRow) {
$this->person = $personRow;
}
public function login($password) {
if ($this->person->password_hash ==
hash('sha256', $this->person->password_salt . $password)) {
return true;
} else {
return false;
}
}
public function setPassword($newPassword) {
$this->person->password_hash = hash('sha256',
$this->person->password_salt . $newPassword);
$this->person->save();
}
}
class Model_Person_Admin extends Model_Person { }
class Model_Person_Associate extends Model_Person { }
$person = Model_Person::get(1);
print "Got object of type ".get_class($person)."\n";
$person->setPassword('potrzebie');
$people = Model_Person::getCollection();
print "Got ".count($people)." people objects:\n";
foreach ($people as $i => $person) {
print "\t$i: ".get_class($person)."\n";
}
"I thought static methods were bad
which is why I was trying to create
the table level methods as instance
methods."
I don't buy into any blanket statement that static is always bad, or singletons are always bad, or goto is always bad, or what have you. People who make such unequivocal statements are looking to oversimplify the issues. Use the language tools appropriately and they'll be good to you.
That said, there's often a tradeoff when you choose one language construct, it makes it easier to do some things while it's harder to do other things. People often point to static making it difficult to write unit test code, and also PHP has some annoying deficiencies related to static and subclassing. But there are also advantages, as we see in this code. You have to judge for yourself whether the advantages outweigh the disadvantages, on a case by case basis.
"Would Zend Framework support a Finder
class?"
I don't think that's necessary.
"Is there a particular reason that you
renamed the find method to be get in
the model class?"
I named the method get() just to be distinct from find(). The "getter" paradigm is associated with OO interfaces, while "finders" are traditionally associated with database stuff. We're trying to design the Domain Model to pretend there's no database involved.
"And would you use continue to use the
same logic to implement specific getBy
and getCollectionBy methods?"
I'd resist creating a generic getBy() method, because it's tempting to make it accept a generic SQL expression, and then pass it on to the data access objects verbatim. This couples the usage of our Domain Model to the underlying database representation.