If we have a code like this:
class Game {
private $_id;
private $_name;
private $_url;
public function __construct($_id,$_name,$_url){
$this->_id = $_id;
$this->_name = $_name;
$this->_url = $_url;
}
}
And we want to simply connect to our Database to get a game by id, where do we place the 'getByID' function?
Do we place it within the 'Game Class' as 'static function', do we put it in the 'Database Connection Class' as 'public function' or do we just put the method in the 'general functions inside the main index.php' as 'function'?
I currenctly have choosen for a 'static function' within the 'Game Class':
public static function getByID($id,$db){
$query = "SELECT * FROM game WHERE id = :id LIMIT 1";
$prepare = array(":id"=>$id);
$result = $db->Precute($query,$prepare);
foreach($result as $r) return new Game($r['id'],$r['name'],$r['url']);
return null;
}
(Precute is a custom function within the Database Class to prepare and execute the query)
How would you approach this?
In proper OOP, a DAL function which returns an instance of a specific class should be static within that class. As a base rule, all functionality related to one specific object should be part of that specific object, as an instance method if invoked on instances or a static method if it creates or manages instances ('factory pattern').
Your function isn't static currently, correct usage would be:
class Game
{
..other functions..
public static function getById($id)
{
..implementation, which can either access central storage or retrieve
the object itself if concurrent edits are not an issue..
}
}
Then elsewhere:
$myGame = Game::getById(684);
You may want to have a look at Doctrine instead of re-inventing the wheel. And even if you do want to make a new wheel, its code samples all follow correct OOP principles.
This Answer takes another approach. Instead of getting Objects from Static Factory. This solution takes a approach of creating a blank object and then calling the database methods to make the object a live representation of a actual row.
first the observations from your question -
an Object/Instance of Game class represents a Row of Table game. And the Game class itself can be taken as a representation of `game' table.
If the above observation is correct along with the assumption that there are more tables with a representation in class hierarchy. You should have a class to represent generic 'Table'
class Table { //The class itself can be made abstract depending upon the exact implementation
protected $_tableName;
protected $_connectionParams;
protected $idAttribute = 'id';
public function __construct($tableName, $connectionParams, $idAttribute){
$this->_connectionParams = $connectionParams;
$this->_tableName = $tableName;
if(isset($idAttribute)) {
$this->idAttribute = $idAttribute;
}
};
private function _getConnection() {
//return $db using $_connectionParams
};
public function getByID($id) {
$this->getByKeyVal($this->idAttribute, $id);
};
public function getByKeyVal($key, $val) {
$query = "SELECT * FROM ". $this->_tableName ." WHERE `". $key ."` = :key LIMIT 1";
$prepare = array(":key"=> $val);
$result = $this->_getConnection()->Precute($query,$prepare);
$this->processRow($result[0]);
};
//This needs to be overridden
public function processRow($row) {
return true;
};
}
Now extend the generic Table class for Game Table
class Game extends Table {
private $_id;
private $_name;
private $_url;
public function __construct($defaults) {
if(isset($defaults) {
if(is_array($defaults)) {
$this->processRow($defaults);
} else {
$this->getByID($defaults);
}
} else {
//Some default setup here if requried
}
$connectionParams = []; //Prepare Connection Params here
parent::__construct('game', $connectionParams);
};
//Override processRow
public function processRow($row) {
if(isset($row['id']) {
$this->_id = $row['id'];
}
$this->_name = $row['name'];
$this->_url = $row['url'];
};
}
Above is a very rough example. The actual Class structure will depend upon your requirements. But the general rule of thumb is to treat a Class as a blueprint of a concrete object. And all the methods related with a Generic Classification should go in there own class.
The getConnection Method itself can be put into a seprate DB connection class and inserted in table via a either mixin pattern or generic class inheritance.
Use the above setup like this
$game_new = new Game(); // for blank object --- for a new row
$game_435 = new Game(435); //row with 435 ID
$game_default = new Game(array( //new row with defaults
'name' => 'Some Name',
'url' => 'Some Url'
));
What you want is a "bucket" full of Game objects. When ever you want a Game Object (representing data in your database), you ask your "bucket" to give it to you. Let me give you an example of how Doctrine2 implements this:
http://docs.doctrine-project.org/en/2.0.x/reference/working-with-objects.html
So where you want to place your "getById" (or as I would do "findById"), is in your "bucket".
// lets presume that the em is an instance of \Doctrine\ORM\EntityManager
// The entity manager does what the name says.
$id = 1234;
$game = $entity_manager->find('MyNamespace\Entity\Game', $id);
$game->setName('My first game!');
// We now tell the em to prepare the object for pushing it back to the "bucket" or database
$entity_manager->persist($game);
// Now we tell the em to actually save stuff
$entity_manager->flush();
This should give you an indication of how to use it. Objects follow the Single Responsibility Principle. You don't ask an object to retrieve itself. You ask the "bucket" to retrieve you an Object.
http://en.wikipedia.org/wiki/Single_responsibility_principle
What if I told you that there are more beautiful ways to put things on their places.
A very simple case might contain 3 basic components to work:
Db framework - Which handles data access.
Table repsotor classes - Which know how to map classes to tables,
how to create classes from table data and how to create data from table classes.
Model or business layer which contain actual classes.
For better understanding imagine you have database object mapper framework.
The framework can be far complex but in few lines we can demonstrate how it`s basic
concepts work.
So the 'Framework':
<?php
//This class is for making link for db framework
class link
{
public $link;
public function __construct ($hostname, $database, $gamename, $password)
{
$this->link = new \PDO ('mysql:host='.$hostname.';dbname='.$database, $gamename, $password);
$this->link->query('use '.$database);
}
public function fetch ($query)
{
$result = $this->link->query($query)->fetch();
}
public function query ($query)
{
return $this->link->query($query);
}
public function error ()
{
return $this->link->errorInfo();
}
}
//This class collects table repositories and connections
class database
{
public $link;
public $tables = array ();
public function __construct ($link)
{
$this->link = $link;
table::$database = $this;
}
}
//This is basic table repositor class
class table
{
public static $database;
}
?>
Now as we have our db framework let us make some table repositor which knows
how to save/load/delete game:
class games extends table
{
public function create ($row)
{
$return = new game ();
$return->id = $row[0];
$return->name = $row[1];
var_export($row);
return $return;
}
public function load ($id=null)
{
if ($id==null)
{
$result = self::$database->link->fetch("select * from games");
if ($result)
{
$return = array();
foreach ($result as $row)
{
$return[$row[0]] = $this->create($row);
}
return $return;
}
}
else
{
$result = self::$database->link->fetch("select * from games where id='".$id."'");
if ($result)
{
return $this->create(reset($result));
}
else
{
echo ("no result");
}
}
}
public function save ($game)
{
if (is_array($save))
{
foreach ($save as $item) $this->save ($item);
}
if ($game->id==null)
{
return self::$database->link->query("insert into games set
name='".$game->name."'");
}
else
{
return self::$database->link->query("update games set name='".$game->name."'
where id='".$game->id."'");
}
}
public function delete ($game)
{
self::$database->link->query ("delete from games where id='".$game->id."'");
}
}
Now we can make our model which in this case will contain actuall game class.
class game
{
public $id;
public $name;
public function __construct ($name=null)
{
$this->name = $name;
}
}
And than actually use it:
$database = new database (new link('127.0.0.1', 'system_db', 'root', '1234'));
$database->tables['games'] = new games();
if (!$database->tables['games']->save (new game('Admin')))
{
var_export($database->link->error());
}
var_export($database->tables['games']->load(2));
For the moment I prefere this pattern for working with db in my projects. Using it I can achieve
that my actuall business objects(In this case class game) will know nothing about
where and how they are saved. This gives me an ability to be indipendent from
actuall storage and focus on project logics.
Also there is one lightweight framework so called db.php (http://dbphp.net) and it even
gives me ability to avoid to write table repositories and even creates/modifies tables
needed for my business classes on the fly but uses almost same concept I described here.
Related
I've been programming in PHP for many years, however only recently started programming with classes. I have the following - basic - user class as follows:
<?php
/* The class for constructing any user's information
*/
class User {
protected $userId, $email, $userGroup;
protected function getEmail() {
return $this->email;
}
protected function getUserId() {
return $this->userId;
}
protected function getUserGroup() {
return $this->userId;
}
public function __construct($userId='') {
if($userId) {
$select = mysql_query("SELECT userId, email, user_group FROM user WHERE userId = '$userId'");
while($user==mysql_fetch_array($select)) {
$this->email = $user[email];
$this->userId = $userId;
$this->userGroup = $user[user_group];
}
}
}
}?>
So I understand I can do the following
<?php
$user = new User($userId);
echo $user->getEmail();
?>
To display the user's email address for a given userId. What I'd like to know is, what would be the best way - using OOP - to display, say, 40 user's emails. Obviously creating 40 user objects would be silly as that's 40 SQL queries. Would you simply make a "users" class that was used for returning an array of multiple users, after doing an SQL given various parameters?
Ie
<?php
$getUsers = new Users('Tom');
// create 'Users' object where name is 'Tom'
print_r($users);
// prints the returned array of users?
?>
Thanks for your help. Hopefully this is clear.
I'd do it something like this (using another class):
class UserRepository {
public function getByName($name) {
$result = mysql_query("SELECT userId, email, user_group FROM user WHERE name = '$name'");
$users = [];
while ($row = mysql_fetch_assoc($result)) {
$user = new User;
$user->email = $row['email'];
$user->userId = $row['userId'];
$user->userGroup = $row['user_group'];
$users[] = $user;
}
return $users;
}
}
Addition:
The following example gives a good idea on how you can make the classes more testable and easy to modify in the future should they need to be:
UserRepositoryInterface
interface UserRepositoryInterface {
public function getByName($name);
public function getByUserId($id);
}
MySqliUserRepository
class MySqliUserRepository implements UserRepositoryInterface {
public function getByName($name) {
// Get by name using mysqli methods here
}
public function getByUserId($id) {
// Get by user id using mysqli methods here
}
}
PDOUserRepository
class PDOUserRepository implements UserRepositoryInterface {
public function getByName($name) {
// Get by name using PDO methods here
}
public function getByUserId($id) {
// Get by user id using PDO methods here
}
}
Usage
class Foo {
protected $userRepository;
public function __construct(UserRepositoryInterface $userRepository) {
$this->userRepository = $userRepository;
}
public function bar() {
$user = $this->userRepository->getByUserId(10);
}
}
Regarding use of mysql_
It may not be exactly how you do it but it'll give you an idea. Also mysql_ is depreciated so its best to use mysqli_ or PDO (my personal recommendation). PDO is also much more OOP friendly.
PDO: http://php.net/manual/en/book.pdo.php
mysqli_: http://php.net/manual/en/book.mysqli.php
Update:
Your individual user class would simply contain information relating to the user. The user class shouldn't contain any way to retrieve a user, that is the job of the repository. So if you want to retrieve 1 user, instead of doing in the User __construct as you currently do, add a method to the UserRepository that looks something like this:
public function getByUserId($id) {
// Select user from db, check only 1 exists, make user object, return.
}
I try to separate my data objects from the DB stuff. In your case, I'd make the following arrangements:
An instance of the User class represents an individual user, either in DB or not. The constructor does not retrieve anything from DB, it just populates class properties.
For users not in DB (e.g., a newly created user) the userId property is NULL (not '').
Methods that do DB stuff expect a database interface (or at least an object) as argument:
public function save(PDO $pdo){
}
There're static methods to fetch stuff from DB where a class instance does not make sense yet; they return either a User instance or a User collection:
public static function fetchById(PDO $pdo, $id){
}
public static function fetchAll(PDO $pdo){
}
When it makes sense, I write a private method to share common code:
private static function fetch(PDO $pdo, array $filter=array()){
$sql = 'SELECT id, email, group
FROM user' . PHP_EOL;
$params = array();
if( isset($filter['id']) ){
$sql .= 'WHERE id=:id';
$params['id'] = $filter['id'];
}
//...
}
public static function fetchById(PDO $pdo, $id){
$data = self::fetch($pdo, array('id' => $id));
if( empty($data) ){
return NULL;
}else{
reset($data);
return curren($data);
}
}
public static function fetchAll(PDO $pdo){
return self::fetch($pdo);
}
The User collection I've mentioned can as simple as an array of User instances, or as elaborate as your own generics implementation.
This way, a typical script looks like this:
// Fetch my details
$me = User::fetchById(1);
// Create a new user
$you = new User(NULL, 'Joe', 'Guests');
$you->save($pdo);
$message = sprintf('User created with ID=%d', $you->userId);
Instead of using the construct to retrieve a user, you could use methods to insert/retrieve users instead.
You can create methods to insert new users, update a particular user, retrieve one particular user or retrieve all (with conditions) in an array of users' objects, etc
I have been working over an year with Magento and have learned it good enough. Now I want to learn Zend, and I'm stuck with models.
I'm used to have entities and collection of entities in Magento, and it's likely that I'll want to use Zend_Db_Table, Zend_Db_Table_Row and/or Zend_Db_Table_Rowset. What I am confused of is the role each class.
I know that I can extend each class, and I understand that in my Product_Table class (that extends Zend_Db_Table_Abstract) it's possible to have private methods that will tell Zend what classes to use for rows and rowsets, however I'm not feeling comfortable with it.
Having this code in Magento:
Example 1
// I understand that maybe I'll use the `new` keyword instead
// Mage::getModel() is only for exemplification
$product = Mage::getModel('catalog/product');
$product->setName('product name');
$product->setPrice(20);
$product->save();
if($id = $product->getId()){
echo 'Product saved with id' . $id;
}
else{
echo 'Error saving product';
}
Example 2
$collection = Mage::getModel('catalog/product')->getCollection();
// this is the limit, I'm ok with other method's name
$collection->setPageSize(10);
$collection->load()
foreach($collection as $product){
echo $product->getName() . ' costs ' . $product->getPrice() . PHP_EOL;
}
How I can implement something similar in Zend Framework? Alternatively if this is a really a bad idea, what are the best practices to implement models in Zend Framework?
Thanks
The Zend team, as mentioned elsewhere, thinks differently about the Model layer than most other PHP Framework creators. Their current thoughts on "the best" way to use their raw tools to provide a Database backed Entity Model can be found in the quick start guide.
That said, most people's solution to Models in Zend Framework is bootstrapping Doctrine.
Here is how I, personally, implement models. I'll use a real life example: my User model.
Whenever I create a model, I use two files and two classes: the model itself (e.g. Application_Model_User) and a mapper object (e.g. Application_Model_UserMapper). The model itself obviously contains the data, methods for saving, deleting, modifying, etc. The mapper object contains methods for fetching model objects, finding objects, etc.
Here are the first few lines of the User model:
class Application_Model_User {
protected $_id;
protected $_name;
protected $_passHash;
protected $_role;
protected $_fullName;
protected $_email;
protected $_created;
protected $_salt;
// End protected properties
For each property, I have a getter and setter method. Example for id:
/* id */
public function getId() {
return $this->_id;
}
public function setId($value) {
$this->_id = (int) $value;
return $this;
}
I also use some standard "magic methods" for exposing public getters and setters (at the bottom of each model):
public function __set($name, $value) {
$method = 'set' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid user property');
}
$this->$method($value);
}
public function __get($name) {
$method = 'get' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid user property');
}
return $this->$method();
}
public function setOptions(array $options) {
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
Example save method:
I validate inside the save() method, using exceptions when the information fails to validate.
public function save() {
// Validate username
if (preg_match("/^[a-zA-Z](\w{6,15})$/", $this->_name) === 0) {
throw new Application_Exception_UserInfoInvalid();
}
// etc.
$db = Zend_Registry::get("db");
// Below, I would check if $this->_id is null. If it is, then we need to "insert" the data into the database. If it isn't, we need to "update" the data. Use $db->insert() or $db->update(). If $this->_id is null, I might also initialize some fields like 'created' or 'salt'.
}
For the mapper object, I have at least two methods: a method that returns a query object for selecting objects, and one that executes the query, initializes and returns objects. I use this so I can manipulate the query in my controller for sorting and filtering.
EDIT
Like I said in my comments, this post: http://weierophinney.net/matthew/archives/202-Model-Infrastructure.html was the inspiration for my current Model implementation.
More options
You can also use Zend_Form to do validation, instead of rolling your own: http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html. I personally don't like this option since I think that Zend_Form is awkward to use and hard to precisely control.
When most people first learn Zend Framework, they learn to subclass Zend_Db related classes. Here is an article that demonstrates this: http://akrabat.com/zend-framework/on-models-in-a-zend-framework-application/
I mentioned that I don't like doing this. Here are a few reasons why:
It's difficult to create models that involve derived/calculated fields (i.e. data populated from other tables)
I found it impossible to incorporate access control (populated from my database)
I like having full control over my models
EDIT 2
For your second example: You can use Zend_Paginator for this. I mentioned that, in your wrapper, you create a method that returns a database query object for selecting objects. Here's my simplified but working user mapper:
class Application_Model_UserMapper {
public function generateSelect() {
$db = Zend_Registry::get("db");
$selectWhat = array(
"users_id",
"name",
"role",
"full_name",
"email",
"DATE_FORMAT(created, '%M %e, %Y at %l:%i:%s %p') as created",
"salt",
"passhash"
);
return $db->select()->from(array("u" => "users"), $selectWhat);
}
public function fetchFromSelect($select) {
$rows = $select->query()->fetchAll();
$results = array();
foreach ($rows as $row) {
$user = new Application_Model_User();
$user->setOptions(array(
"id" => $row["users_id"],
"name" => $row["name"],
"role" => $row["role"],
"fullName" => $row["full_name"],
"email" => $row["email"],
"created" => $row["created"],
"salt" => $row["salt"],
"passHash" => $row["passhash"]
));
$results[] = $user;
}
return $results;
}
}
To handle the paginator, I write a custom Paginator plugin and save it to library/Application/Paginator/Adapter/Users.php. Be sure you have your appnamespace and autoloaderNamespaces[] setup correctly in application.ini. Here is the plugin:
class Application_Paginator_Adapter_Users extends Zend_Paginator_Adapter_DbSelect {
public function getItems($offset, $itemCountPerPage) {
// Simply inject the limit clause and return the result set
$this->_select->limit($itemCountPerPage, $offset);
$userMapper = new Application_Model_UserMapper();
return $userMapper->fetchFromSelect($this->_select);
}
}
In my controller:
// Get the base select statement
$userMapper = new Application_Model_UserMapper();
$select = $userMapper->generateSelect();
// Create our custom paginator instance
$paginator = new Zend_Paginator(new Application_Paginator_Adapter_Users($select));
// Set the current page of results and per page count
$paginator->setCurrentPageNumber($this->_request->getParam("page"));
$paginator->setItemCountPerPage(25);
$this->view->usersPaginator = $paginator;
Then render the paginator in your view script.
I do something similar to SimpleCode's way. My style derives from Pádraic Brady. He has multiple blog posts but the best and quickest resource of his is a online book he wrote: Survive the Deep End!. This link should take you straight to his chapter on Models, Data Mappers, and other cool goodies such as Lazy Loading. The idea is the following:
You have entities such as a User with The properties are defined in an array. All your entities extend an abstract class with magic getter/setters that get from or update this array.
class User extends Entity
{
protected $_data = array(
'user_id' => 0,
'first_name' => null,
'last_name' => null
);
}
class Car extends Entity
{
protected $_data = array(
'car_id' => 0,
'make' => null,
'model' => null
);
}
class Entity
{
public function __construct($data)
{
if(is_array($data))
{
$this->setOptions($data);
}
}
public function __get($key)
{
if(array_key_exists($key, $this->_data)
{
return $this->_data[$key];
}
throw new Exception("Key {$key} not found.");
}
public function __set($key, $value)
{
if(array_key_exists($key, $this->_data))
{
$this->_data[$key] = $value;
}
throw new Exception("Key {$key} not found.");
}
public function setOptions($data)
{
if(is_array($data))
{
foreach($data as $key => $value)
{
$this->__set($key, $value);
}
}
}
public function toArray()
{
return $this->_data;
}
}
$user = new User();
$user->first_name = 'Joey';
$user->last_name = 'Rivera';
echo $user->first_name; // Joey
$car = new Car(array('make' => 'chevy', 'model' => 'corvette'));
echo $car->model; // corvette
Data Mappers to me are separate from the Entities, their job is to do the CRUD (create, read, update, and delete) to the db. So, if we need to load an entity from the db, I call a mapper specific to that entity to load it. For example:
<?php
class UserMapper
{
$_db_table_name = 'UserTable';
$_model_name = 'User';
public function find($id)
{
// validate id first
$table = new $this->_db_table_name();
$rows = $table->find($id);
// make sure you get data
$row = $rows[0]; // pretty sure it returns a collection even if you search for one id
$user = new $this->_model_name($row); // this works if the naming convention matches the user and db table
//else
$user = new $this->_model_name();
foreach($row as $key => $value)
{
$user->$key = $value;
}
return $user;
}
}
$mapper = new UserMapper();
$user = $mapper->find(1); // assuming the user in the previous example was id 1
echo $user->first_name; // Joey
This code is to give an idea of how to architect the code in this way. I didn't test this so I may have created some typos/syntax errors as I wrote it. Like others have mentioned, Zend lets you do what you want with Models, there is no right and wrong it's really up to you. I usually create a table class for every table in the db that I want to work with. So if I have a user table, I usually have a User entity, User Mapper, and a User Table class. The UserTable would extend Zend_Db_Table_Abstract and depending on what I'm doing won't have any methods inside or sometimes I'll overwrite methods like insert or delete depending on my needs. I end up with lots of files but I believe the separation of code makes it much easier to quickly get to where I need to be to add more functionality or fix bug since I know where all the parts of the code would be.
Hope this helps.
Folder Structure
application
--models
----DbTable
------User.php
--controllers
----IndexController.php
--forms
----User.php
--views
----scripts
------index
--------index.phtml
application/models/DbTable/User.php
class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
protected $_primary = 'user_id';
}
application/forms/User.php
class Form_User extends Zend_Form
{
public function init()
{
$this->setAction('')
->setMethod('post');
$user_name = new Zend_Form_Element_Text('user_name');
$user_name->setLabel("Name")->setRequired(true);
$user_password = new Zend_Form_Element_Text('user_password');
$user_password->setLabel("Password")->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setLabel('Save');
$this->addElements(array(
$user_name,
$user_password,
$submit
));
}
}
application/controllers/IndexController.php
class IndexController extends Zend_Controller_Action
{
public function init()
{
}
public function indexAction()
{
$form = new Form_User();
if($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost()))
{
$post = $this->getRequest()->getPost();
unlink($post['submit']);
$ut = new Application_Model_DbTable_User();
if($id = $ut->insert($post))
{
$this->view->message = "User added with id {$id}";
} else {
$this->view->message = "Sorry! Failed to add user";
}
}
$this->view->form = $form;
}
}
application/views/scripts/index/index.phtml
echo $this->message;
echo $this->form;
I have a PHP MVC application using Zend Framework. As presented in the quickstart, I use 3 layers for the model part :
Model (business logic)
Data mapper
Table data gateway (or data access object, i.e. one class per SQL table)
The model is UML designed and totally independent of the DB.
My problem is : I can't have multiple instances of the same "instance/record".
For example : if I get, for example, the user "Chuck Norris" with id=5, this will create a new model instance wich members will be filled by the data mapper (the data mapper query the table data gateway that query the DB). Then, if I change the name to "Duck Norras", don't save it in DB right away, and re-load the same user in another variable, I have "synchronisation" problems... (different instances for the same "record")
Right now, I use the Multiton / Identity Map pattern : like Singleton, but multiple instances indexed by a key (wich is the user ID in our example). But this is complicating my developpement a lot, and my testings too.
How to do it right ?
Identity Map
Edit
In response to this comment:
If I have a "select * from X", how can I skip getting the already loaded records ?
You can't in the query itself, but you can in the logic that loads the rows into entity objects. In pseudo-code:
class Person {}
class PersonMapper {
protected $identity_map = array();
function load($row) {
if (!isset($this->identity_map[$row['id']])) {
$person = new Person();
foreach ($row as $key => $value) {
$person->$key = $value;
}
$this->identity_map[$row['id']] = $person;
}
return $this->identity_map[$row['id']];
}
}
class MappingIterator {
function __construct($resultset, $mapper) {
$this->resultset = $resultset;
$this->mapper = $mapper;
}
function next() {
$row = next($this->resultset);
if ($row) {
return $this->mapper->load($row);
}
}
}
In practice, you'd probably want your MappingIterator to implement Iterator, but I skipped it for brevity.
Keep all loaded model instances in "live model pool". When you load/query a model, first check if it has been already loaded into pool (use primary key or similar concept). If so, return the object (or a reference) from pool. This way all your references point to the same object. My terminology may be incorrect but hopefully you get the idea. Basically the pool acts as a cache between business logic and database.
Multiton
Best option if you want to use a variety of singletons in your project.
<?php
abstract class FactoryAbstract {
protected static $instances = array();
public static function getInstance() {
$className = static::getClassName();
if (!(self::$instances[$className] instanceof $className)) {
self::$instances[$className] = new $className();
}
return self::$instances[$className];
}
public static function removeInstance() {
$className = static::getClassName();
if (array_key_exists($className, self::$instances)) {
unset(self::$instances[$className]);
}
}
final protected static function getClassName() {
return get_called_class();
}
protected function __construct() { }
final protected function __clone() { }
}
abstract class Factory extends FactoryAbstract {
final public static function getInstance() {
return parent::getInstance();
}
final public static function removeInstance() {
parent::removeInstance();
}
}
// using:
class FirstProduct extends Factory {
public $a = [];
}
class SecondProduct extends FirstProduct {
}
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;
print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)
Ok this is not just a PHP question, but I have this problem in PHP so I will post the example in PHP.
Example:
I have three classes, say, data_abstract, person, and student. Now, I have one array of data related to each of those objects.
class person extends data_abstract
{
protected $data; //name, gender, etc
}
class student extends person
{
protected $data; //student_id, etc
}
Now, assuming each of those "data" property is from database tables, for example, table_person, table_student.
Upon instantiation, we will parse the class name by get_class() function, and get data from their related tables.
class data_abstract
{
public function __construct()
{
$name = get_class($this);
$table = 'table_' . $name;
$this->data = DB->query('SELECT * FROM ' . $table);
//whatever DB constant is,
//I'm just trying to get all rows of data from the related table.
}
}
Ok, now the problem, when I instantiate student by $student = new student(); the constructor will get data from table_student for me and put it in $student->data, but I won't be able to get the data from table_person and put those two sets of data into one object.
By extending another class, we can have all the methods (functions) extended and customized (via polymorphism), but extending the attributes/properties of each level of object seems to be hard, at least without some manual construction.
Is there any way in the abstraction level to achieve this?
(thanks for reading, wish I could've made the question clearer.)
If I understand well :
you want all your DB data in $data;
$data must be feed with data from the table 'table_classname';
$data "inherit" from $data that should have been loaded in parents class with that process.
In that case, you should externalize $data feeding so you can overload the data feeding. I edited, now we have a working example :
class DataAbstract // Using caps is much better for class name
{
public function __construct()
{
$this->loadData();
}
public function loadData()
{
// don't use getclass on $this or $this will refer to the children
$table = 'table_' . __CLASS__;
$this->data = array($table);
}
}
class Person extends DataAbstract
{
public function __construct()
{
parent::__construct();
}
public function loadData()
{
parent::loadData();
$table = 'table_' . __CLASS__;
$this->data = array_merge($this->data, array($table));
}
}
class Student extends Person
{
public function __construct()
{
parent::__construct();
}
public function loadData()
{
parent::loadData();
$table = 'table_' . __CLASS__;
$this->data = array_merge($this->data, array($table));
}
}
$c = new student();
print_r($c->data);
Outputs
Array
(
[0] => table_DataAbstract
[1] => table_Person
[2] => table_Student
)
BTW, remember that PHP got introspection, it allows you you to set the fields you need dynamically : maybe cleaner that using a big Array.
Provided you know all the fields names, you can do something like
function populate($data) // get the array from the DB
{
$fieldList = get_class_vars($this); // get the filed list
foreach ($fieldList as $key => $value)
{
$this->$key = $data[$key]; // feed the field one by one with the array
}
}
Personally, I would put the database loading class in an protected method that takes the attribute $tablename, and which loads all the data. Then in the constructor of the class call it by hand, and the parent.
class Person
{
public function __construct() {
$this - > loadData("Person");
}
}
Class Student extends Person
{
public function __construct() {
parent::__construct();
$this - > loadData("Student");
}
}
Now, when you construct Student both data from Student and Person is loaded. if you use array_merge() the 2 data arrays will be merged into one.
It's a matter of style; some people would critisise this by saying that you are now repeating something twice - more work to refactor - but I would say you are decoupling the class names and interfaces from the database design (a good thing) and with the parent call, the code is now more OO.
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.