SitePoint Sponsor |
|
User Tag List
Results 1 to 20 of 20
Thread: Smart Data Access Object
-
May 28, 2007, 13:11 #1
Smart Data Access Object
This Data Acess Object has several benefits.
- Smart database saving (saving database load)
- Methods creation on the fly
- Change listeners (= Observers, EventListeners)
- Method chaining
- Single instance of DAO in every moment
- Possibility to easy verify user input according to data type (string, bool, email, etc) using validators (not shown here)
PHP Code:/**
* <br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License</a>.
* @package MVCS
* @copyright © 2007 Remiya
*/
abstract class MVCS_DAO{
private $fields = array();
private $states = array();
private $types = array();
private $values = array();
private $listeners = array();
final private function __construct($db){
echo "Loading DAO...<br>";
$this->__load_structure();
foreach($this->__structure as $field=>$type){
$this->fields[]=$field;
$this->types[]=$type;
}
$this->__load();
}
abstract public static function getInstance();
abstract public function __load_structure();
final private function __call($method, $arguments){
if(in_array($method,$this->fields)==false)trigger_error('ERROR: Field <b>'.$method.'</b> not in '.get_class($this).' structure!',E_USER_ERROR);
if(count($arguments)==0){
return $this->__get($method);
}else{
$this->__set($method,$arguments[0]);
if(isset($this->listeners['on_'.$method])){
$this->__update();
foreach($this->listeners['on_'.$method] as $listener){
echo "Updating listener ".$listener."<br>";
//$listener->update();
}
}
}
return $this;
}
final private function __set($field,$value){
echo "Setting <b>$field</b> to $value...<br>";
$pointer = array_search($field,$this->fields);
$this->values[$pointer] = $value;
$this->states[$pointer] = "changed";
}
final private function __load(){
echo "Loading DAO from database...<br>";
}
final public function __update(){
if(in_array("changed",$this->states)){
$fields = array();
for($i=0;$i<count($this->fields);$i++){
if($this->states[$i] == "changed"){
echo "Changes found in ".$this->fields[$i]."<br>";
$fields[$this->fields[$i]] = $this->values[$i];
}
}
echo "Saving the fields <b>".implode(",",array_keys($fields))."</b> of DAO in database...<br>";
$this->states = array();
}else{
echo "No changes found...<br>";
}
}
final private function __get($field){
echo "Getting <b>$field</b>...<br>";
$pointer = array_search($field,$this->fields);
return $this->values[$pointer];
}
final public function add_listenter($event,$listener){
echo "Adding listener ".$listener." updated on ".$event."<br>";
$this->listeners[$event][] = $listener;
}
final public function __destruct(){
$this->__update();
echo "Deleting DAO...<br>";
}
}
PHP Code:class User extends MVCS_DAO{
private static $_instance;
public static function getInstance(){
if (null === self::$_instance) {self::$_instance = new self($db);}
return self::$_instance;
}
function __load_structure(){
$this->__structure = array(
"username"=>"string",
"password"=>"string",
"firstname"=>"string",
"lastname"=>"string"
);
}
}
PHP Code:// Creating a new User
$user = User::getInstance();
// Adding listeners to be updated when the first name is changed in the database
$user->add_listenter("on_firstname","FIRST NAME LISTENER 1");
$user->add_listenter("on_firstname","FIRST NAME LISTENER 2");
// Changing the user's username to TOMMY and forcing the saving to database
// If no update is forced the saving will occur on object destruction
$user->username("TOMMY")->__update();
// Chaging the user's username and last name and forcing saving to database
// If no update is forced the saving will occur on object destruction
// Changing first name notifies the listeners
$user->firstname("Tom")->lastname("Curtis")->__update();
// Example of getting parameters
echo "User ".$user->username()." is ".$user->firstname()." ".$user->lastname()."<br />";
// Example forcing update, that doesn't get saved to the database, because no change in the User properties are found
$user->__update();
// On destruction of object, the object is checked for changes
// If no changes found object dies, else object is saved to database
// and object dies
Loading DAO...
Loading DAO from database...
Adding listener FIRST NAME LISTENER 1 updated on on_firstname
Adding listener FIRST NAME LISTENER 2 updated on on_firstname
Setting username to TOMMY...
Changes found in username
Saving the fields username of DAO in database...
Setting firstname to Tom...
Changes found in firstname
Saving the fields firstname of DAO in database...
Updating listener FIRST NAME LISTENER 1
Updating listener FIRST NAME LISTENER 2
Setting lastname to Curtis...
Changes found in lastname
Saving the fields lastname of DAO in database...
Getting username...
Getting firstname...
Getting lastname...
User TOMMY is Tom Curtis
No changes found...
No changes found...
Deleting DAO...
-
May 28, 2007, 13:39 #2
- Join Date
- Mar 2005
- Posts
- 423
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I'm curious as to why you are using double underscores on method names - i thought they were reserved by php as possible magic methods?
-
May 28, 2007, 14:56 #3
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
You implement the class as a singleton, yet it seems to manage a single record. That is going to give you trouble.
-
May 28, 2007, 15:09 #4
-
May 28, 2007, 15:15 #5
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
-
May 28, 2007, 15:24 #6
I see your point now. Yes, this is a simple example with one record.
I am thinking on how to implement the multiple records now. May be they will be defined also in the structure.
Any ideas welcome.
-
May 28, 2007, 21:01 #7
The structure should be read like this
function __load_structure(){
$this->__structure = array(
"username"=>array("type"=>"string","table"=>"users"),
"password"=>array("type"=>"string","table"=>"users"),
"firstname"=>array("type"=>"string","table"=>"users"),
"lastname"=>array("type"=>"string","table"=>"users"),
"lastname"=>array("type"=>"string","table"=>"users"),
"photos"=>array( "type"=>"Photo"," table"=>"photos", "by"=>"username"),
"albums"=>array("type"=>"string","table"=>"users"),
);
}
-
May 29, 2007, 01:50 #8
- Join Date
- Mar 2007
- Location
- Germany
- Posts
- 428
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Maybe a primary key could help you with the singleton problem.
You could use a singleton object for ech row identified by its unique ID.
That's only what came to my mind after reading the thread, didn't really think it over so it may be stupid crap.
-
May 29, 2007, 04:04 #9
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
You are thinking of an Identity Map but personally I don't believe that pattern has much use with PHPs architecture (everything dies at end of request, even bacteria) so it would be fruitless...
Unless of course, you use some sort of cache, but your server would need to have that supported.
-
May 29, 2007, 04:48 #10
- Join Date
- May 2007
- Location
- The Netherlands
- Posts
- 282
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Couple of notes on the implementation.
* Why declare everything, even private methods, as final? Final private methods don't really make any sense.
* Nor do abstract static methods.
* Why does __load_structure start with two underscores and is public? (You shouldn't start your own methods with two underscores anyway.
* I don't know how you implemented the data validation part, but it really shouldn't be coupled to a data access object.
* DAO decouple the actual data (using DTOs, Data Transfer Objects) from the storage mechanism while your implementation actually keeps track of the data itself.
* Think of data migration and how you're going to handle changes in the data model it's an important aspect that is often overlooked, Ruby On Rails could be a source of inspiration here.
-
May 29, 2007, 05:29 #11
- Join Date
- Sep 2005
- Posts
- 122
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
You've obviously never worked on large applications with potentially large object graphs. Identity maps are crucial in ensuring the same object is never reconstituted more than once from the database. It's also necessary for bi-directional associations where you can break a cyclic dependency by using lazy loading. When the lazy loaded object is reconstituted, it can simply pull the associated object from the identity map.
-
May 29, 2007, 08:21 #12
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Obviously?
I don't particularly favour the Identity Map in regards to PHP for the reason I gave earlier; It's got nothing to do with scale at all at the end of the day, when you consider that you have that Identity Map to maintain from one request to another.
A cache would help, but only in so far, it's not a solution. What happens when your Identity Map has stale data, or begins to grow? When do you make the choice between the convienence and performance in that case?
Don't talk to me about the benefits as I've been down that road myself; I've bought the -beep- t-shirt, mug and key ring. I can tell you from my own experience, it's just not worth the hassle of having an Identity Map with PHP and it's architecture, presently as it stands.
I agree with the point of Lazy Initialisation, which does have it's uses but I have to question your design decisions.
-
May 29, 2007, 08:27 #13
- Join Date
- May 2007
- Location
- The Netherlands
- Posts
- 282
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Although the article is on O/R mappers and caching, an Identity map basically serves the same purpose. Frans Bouma explains just how reasonable it is to have an Identity map for performance reasons. (Spoiler: it's not).
Design patterns: trying to do Smalltalk in Java.
I blog too, you know.
-
May 29, 2007, 09:43 #14
Here is the updated version. Changes from first:
- dynamic table loading on runtime
- initialization structure changed adding key to get records
- no more double undescore (__) but (_)
PHP Code:/**
* <br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License</a>.
* @package MVCS
* @copyright © 2007 Remiya
*/
abstract class MVCS_DAO{
private $fields = array(); private $field_tables = array();
private $listeners = array();
private $tables = array();
protected $structure = null;
protected function __construct(){
echo "Loading <b>".get_class($this)."</b> DAO...<br>";
$this->_load_structure();
foreach($this->structure as $field=>$value){
$this->fields[$field]=array();
$this->fields[$field]['table']=$value['table'];
$this->fields[$field]['type']=$value['type'];
$this->fields[$field]['changed']=false;
$this->fields[$field]['value']=null;
// TABLES
if(isset($this->tables[$value['table']])==false){
$this->tables[$value['table']]=array();
$this->tables[$value['table']]['loaded'] = false;
$this->tables[$value['table']]['fields'] = array();
}
if(isset($this->tables[$value['table']]['by'])==false)$this->tables[$value['table']]['by']=$value['by'];
$this->tables[$value['table']]['fields']['name'] = $field;
$this->tables[$value['table']]['fields']['type'] = $value['type'];
}
}
abstract public static function getInstance();
abstract protected function _load_structure();
final private function __call($method, $arguments){
if(array_key_exists($method,$this->fields)===false)trigger_error('ERROR: Field <b>'.$method.'</b> not in '.get_class($this).' structure!',E_USER_ERROR);
if(count($arguments)==0){
return $this->__get($method);
}else{
$this->__set($method,$arguments[0]);
if(isset($this->listeners['on_'.$method])){
$this->_update();
foreach($this->listeners['on_'.$method] as $listener){
echo "Updating listener <b>".$listener."</b><br>";
//$listener->update();
}
}
}
return $this;
}
final private function __get($field){
echo "Getting <b>$field</b>...<br>";
// Check if table loaded
$table = $this->fields[$field]['table'];
if($this->tables[$table]['loaded']==false){$this->_load_table($this->fields[$field]['table']);}
return $this->fields[$field]['value'];
}
final private function __set($field,$value){
echo "Seting <b>$field</b> to <b>$value</b>...<br>";
// Check if table loaded
$table = $this->fields[$field]['table'];
if($this->tables[$table]['loaded']==false){$this->_load_table($this->fields[$field]['table']);}
$this->fields[$field]['changed'] = true;
$this->fields[$field]['value'] = $value;
}
final private function _load_table($table_name){
echo "Loading table <b>$table_name</b> from database...<br>";
$this->tables[$table_name]['loaded']=true;
}
final public function _update(){
$changed_fields = array();
foreach($this->fields as $field=>$properties){
if($properties['changed']==true){$changed_fields[] = $field;}
}
if(count($changed_fields)>0){
echo "Saving the fields <b>".implode("</b>, <b>",$changed_fields)."</b> of DAO in database...<br>";
foreach(array_keys($this->fields) as $field){
$this->fields[$field]['changed']=false;
}
}else{
echo "No changes found...<br>";
}
}
final public function _add_listenter($event,$listener){
echo "Adding listener ".$listener." updated on ".$event."<br>";
$this->listeners[$event][] = $listener;
}
final public function __destruct(){
echo "In Destructor: Updating if changes found...<br>";
$this->_update();
echo "Deleting <b>".get_class($this)."</b> DAO...<br>";
}
}
class User extends MVCS_DAO{
private static $_instance;
protected function __construct(){
parent::__construct();
}
public static function getInstance(){
if (null === self::$_instance) {self::$_instance = new self();}
return self::$_instance;
}
protected function _load_structure(){
$this->structure = array(
"username"=>array("type"=>"string", "table"=>"users", "by"=>"username"),
"password"=>array("type"=>"string", "table"=>"users", "by"=>"username"),
"firstname"=>array("type"=>"string","table"=>"users", "by"=>"username"),
"lastname"=>array("type"=>"string", "table"=>"users", "by"=>"username"),
"email" =>array("type"=>"string", "table"=>"users", "by"=>"username"),
"photos"=>array( "type"=>"array", "table"=>"photos","by"=>"username"),
"albums"=>array("type"=>"array", "table"=>"albums","by"=>"username")
);
}
}
PHP Code:// Creating new user
$user = User::getInstance();
// Adding listeners
$user->_add_listenter("on_firstname","FIRST NAME LISTENER 1");
$user->_add_listenter("on_firstname","FIRST NAME LISTENER 2");
// Changing username and forcing update
// Table "users" is loaded on runtime
$user->username("TOMMY")->_update();
// Changing firstname and forcing update
$user->firstname("Tom")->lastname("Curtis")->_update();
echo "User ".$user->username()." is ".$user->firstname()." ".$user->lastname()."<br />";
// Table "photos" loaded on runtime
echo "Getting user photos...<br />";
$user->photos();
// Changing email without forcing update
$user->email("tommy@email.com");
// The email get saved at object dying
Loading User DAO...
Adding listener FIRST NAME LISTENER 1 updated on on_firstname
Adding listener FIRST NAME LISTENER 2 updated on on_firstname
Seting username to TOMMY...
Loading table users from database...
Saving the fields username of DAO in database...
Seting firstname to Tom...
Saving the fields firstname of DAO in database...
Updating listener FIRST NAME LISTENER 1
Updating listener FIRST NAME LISTENER 2
Seting lastname to Curtis...
Saving the fields lastname of DAO in database...
Getting username...
Getting firstname...
Getting lastname...
User TOMMY is Tom Curtis
Getting user photos...
Getting photos...
Loading table photos from database...
Seting email to tommy@email.com...
In Destructor: Updating if changes found...
Saving the fields email of DAO in database...
Deleting User DAO...
-
May 30, 2007, 20:04 #15
- Join Date
- Sep 2005
- Posts
- 122
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
You don't need to persist the Identity Map over requests. There are still benefits to be had from scrapping it and rebuilding it per request. I already gave an example. Like I said, when you work on a large application where the same object could appear deep within two separate object graphs and then the same object is edited in both graphs, the benefits of the identity map become abundantly clear and absolutely necessary.
-
May 31, 2007, 07:52 #16
- Join Date
- May 2004
- Location
- Central USA
- Posts
- 806
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Remiya, I don't get why you have underscores in your functions at all. It seems much more natural to do a $user->update() or $user->save() without the underscore. If you are looking to avoid conflicts with column names as function calls, you may want to reconsider the way you get and set column names. The traditional DAO will use functions appended with "get" and "set" to avoid the confusion and conflicts with other class functions, like:
PHP Code:$user->getUsername();
$user->getLastname();
$user->setUsername("bob");
$user->save();
PHP Code:$user->username;
$user->lastname;
$user->username = "bob";
$user->save();
Stackbox CMS - Full edit-on-page drag-and-drop CMS
Autoridge - Vehicle information & maintenance part numbers
Twitter | Blog | Online Javascript Compressor
-
May 31, 2007, 08:17 #17
You almost answered your question. I want to avoid conflicts with column names as function calls, and I want to give the user as much freedom as possible.
I personally don't like this approach. In addition to freedom of the user, I want to add extreme ease of use.
The _update function is actually "FORCED UPDATE", and is added for the user to be able to update the database records, just before notifying the observers, because some of them may need also to make database calculations. (PS. The 3 calling of _update are just for demo purposes)
In the most usual usage, you don't need to call the _update finction, nor _destruct, because the object is automaticly updated when dying with the end of the PHP script.Last edited by REMIYA; May 31, 2007 at 13:36.
-
May 31, 2007, 11:15 #18
- Join Date
- May 2004
- Location
- Central USA
- Posts
- 806
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
The reason ActiveRecord patterns do this is because if you think about what your object represents, it makes perfect semantic sense to do it this way. Class variables are referred to as properties of that class. Since objects are made to represent actual things, your User object will represent an actual User of your system. That actual User will have certain properties as well, such as a username, a first name, a last name, etc., so it does make sense to get and assign the values this way, and is very easily readable.
Functions, on the other hand, exist to break apart and isolate different pieces of logic. So semantically, it just doesn't make sense to use a function to retrieve or set data without a more clear naming convention. The code could become confusing to read the way it is setup now. When you called $user->photos() I actually looked back at the User class and was expecting to see a declared function called photos. How does one know what this function will do at a glance? I don't necessarily expect a function called photos() to return only the raw data in the database column. Being a function, I would expect it to do some sort of logic like getting the actual <img> tags for each photo or assembling the photos into an array for use in the template, etc.
I also would not depend on the object getting destroyed as a way of saving - what happens if an error is encountered and you need to discard the changes? There is no current function to do that, and you can't destroy the object because that will automatically save and update the record. You're better off requiring an explicit function call after all your changes to $user->update() to save the record, both for error handling and readability (How does a programmer know the changes are automatically saved without reading all your class code?).Stackbox CMS - Full edit-on-page drag-and-drop CMS
Autoridge - Vehicle information & maintenance part numbers
Twitter | Blog | Online Javascript Compressor
-
May 31, 2007, 14:08 #19
Functions isolate different pieces of logic. Absolutely. Someone asked about validation. This is exactly where this comes in.
At the set method the validation is to take part automaticly (NOT SHOWN HERE FOR THE BASIC CONCEPT IS SHOWN). So it will check if Username is of type String, if Email is valid email, etc. If error occurs it is trigerred before any database record is saved.
About the naming convention, it is perfectly clear and concise. And the readability it is much, much easier. Look at this:
PHP Code:$user1->reads("books")->how_often("2 per week")->pays_with("cash");
$user2->reads("magazines")->how_often("1 per month")->pays_with("Visa");
against:
PHP Code:$user1->reads = "books";
$user1->how_often = "2 per week";
$user1->pays_with = "cash"
// If you don't call this your object will never get saved LOL
$user1->save();
-
May 31, 2007, 19:55 #20
- Join Date
- May 2004
- Location
- Central USA
- Posts
- 806
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Everyone has different tastes on what looks and reads better. I personally prefer things on their own line. You obviously have different preferences.
The example you used is quite the academic example. Let's try using code we might actually see, like with your User class.
PHP Code:$user->username("test")->firstname("john")->lastname("doe");
PHP Code:$user->username = "test";
$user->firstname = "john";
$user->lastname = "doe";
$user->save();
You mock the use of the required $user->save(); statement, but you don't realize that it's there to add clarity and concreteness to the code. Your lack of any such statement in the example you gave adds to the confusion. And as I said in my previous post, what if you encounter an error? You have not provided a way to discard the changes, and if you destroy the object it will automatically save. The save is passive, but it needs to be explicit.Stackbox CMS - Full edit-on-page drag-and-drop CMS
Autoridge - Vehicle information & maintenance part numbers
Twitter | Blog | Online Javascript Compressor
Bookmarks