Authentication Poll & Community Design [PLEASE JOIN!]
Moderator: General Moderators
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
Gateway is the simplest, but breaks down when there's no clean correspondence between domain objects and the relational database. Furthermore, you can't "build" a mapper off a gateway, while you can do the vice versa. However, mappers are complex beasts and VERY difficult for newbies to understand. However, my understanding is that if we create a miniature domain model to handle the authentication (which is probably a GOOD idea, considering the possible complexity of the whole thing: we should go OOP), mappers are best suited for the task.
So... how are the unit tests going?
Edit - I've added proof-of-concept interfaces for all three possible solutions (there's a fourth, Column Data Gateway, but we're not going to use that). A mapper would also need an Identity Map, a Key Generator, and, perhaps a Unit of Work. A lot of extra components, but I've already got implementations of them lying around in other projects. But, still, if we add that stuff, we go down the "framework" route.
ALSO - I had a revelation. If the column names can be anything at all, depending on what the user names them, we CANNOT use associative returns, because they wouldn't mean anything.
So... how are the unit tests going?
Edit - I've added proof-of-concept interfaces for all three possible solutions (there's a fourth, Column Data Gateway, but we're not going to use that). A mapper would also need an Identity Map, a Key Generator, and, perhaps a Unit of Work. A lot of extra components, but I've already got implementations of them lying around in other projects. But, still, if we add that stuff, we go down the "framework" route.
ALSO - I had a revelation. If the column names can be anything at all, depending on what the user names them, we CANNOT use associative returns, because they wouldn't mean anything.
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
I think we are totally over thinking this DB setup. I understand DBResult and can extend that easily. However I don't think we have to update anything. Are we going to also implement the profile also, as well as the administrative pages and functions? We can provide support for it, but not do it ourselves.
To be honest, I was hoping for something like:
Or this possible with the gateway and Mapper thingy? Because if it is then I'll be quiet.
To be honest, I was hoping for something like:
Code: Select all
//$authtype= new DBMap($column, $table, $type);
$username = new DBMap('username', 'user', AuthTools::USERNAME);
$password = new DBMap('pass', 'user', AuthTools::PASSWORD);
$authtools->addColumn($username);
$authtools->addColumn($password);- Maugrim_The_Reaper
- DevNet Master
- Posts: 2704
- Joined: Tue Nov 02, 2004 5:43 am
- Location: Ireland
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
I disagree. DB access is one of those central issues that, once a decision is made, is VERY difficult to refactor. It's worth getting it right.I agree we're overdoing the DB setup - get something that works first, then worry about whether it needs refactoring...
Not exactly sure what the code does, so can't tell you.To be honest, I was hoping for something like:
I disagree, I have done DB refactoring before and it isn't all that difficult.
Okay, it is terrible, but just an example.
I've done some AJAX, just the other day and it isn't all that difficult, once you over the bashing your head on to the table part. Seriously, the reason why I like PHP is because if it crashes and burns you have some idea where you went wrong.
"xmlTransport is Not a Constructor" really doesn't tell you anything, since like, looking at it, yes it is an constructor, should I kill a sheep for you oh hail great JavaScript Satan?
Anyway, I fixed it so it is all good. I'm not talking about right now, I'm talking about once Authtools is nearing completion.
Code: Select all
$query = mysql_query("SELECT ". $username->column.", ". $password->column ." FROM ". $username->table ." WHERE ".$username->column ."='$_POST[username]'");I've done some AJAX, just the other day and it isn't all that difficult, once you over the bashing your head on to the table part. Seriously, the reason why I like PHP is because if it crashes and burns you have some idea where you went wrong.
"xmlTransport is Not a Constructor" really doesn't tell you anything, since like, looking at it, yes it is an constructor, should I kill a sheep for you oh hail great JavaScript Satan?
Anyway, I fixed it so it is all good. I'm not talking about right now, I'm talking about once Authtools is nearing completion.
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
How big was the codebase you were working with? And are we talking about the same type of refactoring? I'm referring to the changes throughout all files that interact with the database if the API changes.I disagree, I have done DB refactoring before and it isn't all that difficult.
Still doesn't clarify. Why don't you tell me what DBMap and AuthTools are?Okay, it is terrible, but just an example.
To be honest the DB abstraction was only a total of 5 classes in the factory model and it was optimization. Still, anything can be refactored given enough time. It isn't much of an issue. However, I don't think telling you that we are going to scrap the DB model. What I meant to say is that I would like to develop a simpler model for those who are of novice level. Even for me, until I see a finished example, I'll have no idea where to begin on how to use it. So really, it is more, "oh man, this is why I build my own," and I'm not dissing the system. I'm still waiting and I'm most likely wrong about it. I just hope I am and that I eventually understand how to use it. Probably just needs one of those Auto functions.
DBMap
Maps to the table and username column and the type assigns the value so that the other classes now how to deal with it. I got the idea from another Auth system that allowed you to choose the table and column for which the username column was retrieved. The same for the password.
I would say that the AuthTools would probably be the gateway perhaps where it translates the maps to the rest of the system for use.
DBMap
Maps to the table and username column and the type assigns the value so that the other classes now how to deal with it. I got the idea from another Auth system that allowed you to choose the table and column for which the username column was retrieved. The same for the password.
I would say that the AuthTools would probably be the gateway perhaps where it translates the maps to the rest of the system for use.
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
Well, the abstraction should make it easy to use. Here's how each of the different patterns would work...
Mapper:
Active Record:
Gateway:
Mapper:
Code: Select all
$user = $mapper->findByUsername($input['username']);
$user->password->validatePassword($input['password']);Code: Select all
$user = User::findByUsername($input['username']);
$user->password->validatePassword($input['password']);Code: Select all
$user_data = $gateway->findUserByUsername($input['username']);
$password = new Password($user_data['password']);
$password->validatePassword($input['password']);I usually don't love it when I'm wrong. Keep doing what you're doing. I can really give you any review since you have left me back at DBResult and Iterator.Ambush Commander wrote:Well, the abstraction should make it easy to use. Here's how each of the different patterns would work...
This is sweet, the names could probably be changed, but even n00bs and people like me can get this.Mapper:Code: Select all
$user = $mapper->findByUsername($input['username']); $user->password->validatePassword($input['password']);
Don't like it, The use of static methods may give it a speed up from the Mapper, but it is 'harsh' to look at.Active Record:Code: Select all
$user = User::findByUsername($input['username']); $user->password->validatePassword($input['password']);
Between, the Mapper and the Gateway, I'll say I may just use your DB Abstraction on my other projects if you so allow it.Gateway:Code: Select all
$user_data = $gateway->findUserByUsername($input['username']); $password = new Password($user_data['password']); $password->validatePassword($input['password']);
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
If you've got some time, I strongly suggest you read Patterns of Enterprise Application Architecture. None of this stuff is mine.
It's all from that book.
The trouble with mappers are they're most complicated. Most users won't be able to overload them effectively without understanding the pattern in the first place. On the other hand, I love the cleanness of the interface: it goes straight to OOP.
The gateway is is the closest jump from manually executing the SQL throughout the domain code. All it does is wrap the SQL. This means that, in it's simplest form, you'd have things like:
While the mapper takes it a step further, and accepts objects (which, understandably, would be a lot easier to do). Also, the mapper tends to do a lot of SQL generation for basic commands, which makes it somewhat a beast to understand internally.
I implemented a mapper a while ago, it looked like this:
and a concrete implementation:
This code is doing a lot of stuff, including automatic, randomly executed expiration queries and identity map registration. Imagine if a user decided they wanted to use flat files...
The trouble with mappers are they're most complicated. Most users won't be able to overload them effectively without understanding the pattern in the first place. On the other hand, I love the cleanness of the interface: it goes straight to OOP.
The gateway is is the closest jump from manually executing the SQL throughout the domain code. All it does is wrap the SQL. This means that, in it's simplest form, you'd have things like:
Code: Select all
$gateway->updatePassword($user['id'], $user['password'], $user['salt'], $user['algorithm']);I implemented a mapper a while ago, it looked like this:
Code: Select all
<?php
class Mapper
{
var $_db;
var $_KeyGenerator;
var $_IdentityMap;
var $_table; //abstract
var $_primaryKey; //abstract
var $_name; //abstract
//timer related
var $_interval; //optional abstract
var $_random; //optional abstract
function Mapper(&$db, &$map, &$key, &$plugin) {
$this->_db =& $db;
$this->_KeyGenerator =& $key;
$this->_IdentityMap =& $map;
//rename tablename based on prefix
$this->_table = $plugin->getDatabasePrefix() . $this->_table;
}
function _findStatement($field = false) {
if (!$field) {
$field = $this->_primaryKey;
}
return " SELECT " . $this->_columns .
" FROM `" . $this->_table . "` " .
" WHERE $field = ? ";
}
function &_abstractFind($id) {
$result =& $this->_getObject($id);
if ($result != null) return $result;
$rs =& $this->_db->Execute($this->_findStatement(), array($id));
if (!$rs) return null;
$result =& $this->_load(&$rs);
return $result;
}
function &_abstractFindUnique($field, $value) {
$result =& $this->_getObjectByUnique($field,$value);
if ($result != null) return $result;
$rs =& $this->_db->Execute($this->_findStatement($field), array($value));
if (!$rs) return null;
$result =& $this->_load(&$rs);
return $result;
}
function &_abstractFindAll() {
$rs =& $this->_db->Execute("SELECT " . $this->_columns .
" FROM `" . $this->_table . "`");
if (!$rs) return array();
$result =& $this->_loadAll(&$rs);
return $result;
}
function _abstractDelete(&$obj) {
$this->_db->execute(
"DELETE FROM `{$this->_table}` WHERE id = ? LIMIT 1;",
array($obj->getID()));
}
function _abstractInsert(&$subject) {
$id = $this->_KeyGenerator->nextKey();
$subject->setID($id);
$insertArray = $this->_doInsert(&$subject);
$this->_db->execute($this->_insertStatement(), $insertArray);
$this->_setObject(&$subject);
$this->_setObjectByUnique(&$subject);
}
function _abstractDeleteExpired() {
if (mt_rand(0,$this->_random)) return;
$this->_db->execute(
"DELETE FROM `{$this->_table}` WHERE timer_expiration < ?",
array(time())
);
}
function &_loadFromRow($row) {
if (empty($row)) return null;
$id = (int) $row[$this->_primaryKey];
$result =& $this->_getObject($id);
if ($result != null) return $result;
$result =& $this->_doLoad($row);
$this->_setObject(&$result);
$this->_setObjectByUnique(&$result);
return $result;
}
function &_load($rs) {
$array = $rs->GetArray();
if (empty($array)) return null;
return $this->_loadFromRow($array[0]);
}
function &_loadAll($rs) {
$resultarray = array();
$array = $rs->GetArray();
foreach($array as $row) {
$resultarray[] =& $this->_loadFromRow($row);
}
return $resultarray;
}
function _updateTimer(&$obj) {
$timer =& $obj->getTimer();
if ($timer->getExpiration() === null) {
$timer->setInterval($this->_interval);
$timer->reset();
}
}
function _insertStatement() {} //abstract
function &_doLoad(&$rs) {} //abstract
function _doInsert($subject) {} //abstract
function &_getObject($id) {return null;} //abstract
function &_getObjectByUnique($field, $value) {return null;} //abstract
function _setObject(&$obj) {} //abstract
function _setObjectByUnique(&$obj) {} //abstract
}
?>Code: Select all
<?php
class Mapper_Token extends Mapper
{
var $_name = 'Token';
var $_table = 'tokens';
var $_primaryKey = 'id';
var $_columns = ' `id`, `value`, `creation`, `timer_expiration`, `user_id` ';
var $_interval = 1814400; //3 weeks
var $_random = 1;
function find($id) {
return $this->_abstractFind($id);
$this->deleteExpired();
}
function insert(&$obj) {
$this->_updateTimer(&$obj);
$this->_abstractInsert(&$obj);
$this->deleteExpired();
}
function delete(&$obj) {
$this->_abstractDelete(&$obj);
$this->deleteExpired();
}
function deleteExpired() {
$this->_abstractDeleteExpired();
}
function &_getObject($id) {
$token =& $this->_IdentityMap->getToken($id);
return $token;
}
function &_getObjectByUnique($field, $value) {return null;}
function _setObject(&$obj) {
$this->_IdentityMap->addToken(&$obj);
}
function _setObjectByUnique(&$obj) {}
function &_doLoad($fields) {
if (!$fields) return false;
if ($fields['timer_expiration'] <= time()) return null;
$Registry =& Registry::instance();
$MapperUser =& $Registry->getMapperUser();
$obj =& new Token((int) $fields['id'],$fields['value'],
(int) $fields['creation'],new Timer($fields['timer_expiration']),
$MapperUser->find((int) $fields['user_id']));
return $obj;
}
function _insertStatement() {
return "INSERT INTO `{$this->_table}` ({$this->_columns}) VALUES (?,?,?,?,?)";
}
function _doInsert(&$obj) {
$timer =& $obj->getTimer();
$user =& $obj->getUser();
return array($obj->getID(), $obj->getValue(), $obj->getCreation(),
$timer->getExpiration(), $user->getID());
}
}
?>I see, so the Mapper would be easy only if there doesn't need to be any changes. I had it the other way around then. Both are still nice and I wouldn't mind playing around with it in my mind. Probably drive me even more crazy, but that is okay.
If you want people to use it, then make it easy to use.
If you want people to use it, then make it easy to use.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
-
alex.barylski
- DevNet Evangelist
- Posts: 6267
- Joined: Tue Dec 21, 2004 5:00 pm
- Location: Winnipeg
Huh...Seriously, the reason why I like PHP is because if it crashes and burns you have some idea where you went wrong.
I disagree...from other programming languages perspective...
Being loosely typed and very flexible has it's advantages...but knowing what went wrong isn't one of them
On the contrary...PHP like other laymen languages will let you blindly enter characters and likely output something...perhaps not useful...but still it's something...
IMHO, thats not very helpful in the sense you have suggested...
Cheers