
Originally Posted by
been
I still strongly believe that there's a distinct difference between client code not living up to a contract and invalid user input
What if you consider the user to be a client?

Originally Posted by
been
So what does validate() look like in this case?
The assert which calls an exception was meant to follow "halt as soon as possible" model, an assert which would fit "tell the client what the problems are" would be more like this:
PHP Code:
class ActiveRecordBase
{
var $_errors = array();
# remember that array() == false
function is_valid ( )
{
$this->_errors = array();
$this->_validate();
return !$this->_errors;
}
###
function assert ( $field, $valid, $message = 'Invalid' )
{
# isset lets us return only the first error for each field,
# so that "missing" can be shown instead of "invlid".
if (!isset($this->_errors[$field]) && !$valid)
{
$this->_errors[$field] = $message;
}
}
###
function save()
{
if ($this->is_valid())
{
# more code...
}
}
###
# more code...
}
class Shape extends ActiveRecordBase
{
function _validate ( )
{
$this->assert('color', in_array($this->color, array("green", "orange", "purple")), "What is this colour?");
}
}
(You could tuck away the validation code up in the inheritance tree, by having ActiveRecordBase inherit from ValidatingObject for example.)
Off Topic:
If you wanted to make the above more flexable, you could add an assert_rule method too, which would act a bit like the addRule method in the ApplicaitonController thread. I do think though that using an object where a boolean works is overkill and leads to bloated code, so should be treated as the exception rather than the rule.
But without affecting other code, it could look like this:
PHP Code:
class ActiveRecordBase
{
function assert_rule ( $field, $rule, $message = 'Invalid' )
{
if (!isset($this->_errors[$field]) && !$rule->passes())
{
$this->_errors[$field] = $message;
}
}
}
A case where you might like to have the extra flexibility would be if you wanted to test to see if a username was unique, but you consider the database call to be expensive, so you only want to actually test it if the username is otherwise valid. Using objects is our best tool IMO when we don't have easy access to use anonymous functions.

Originally Posted by
been
But, it's impossible to predict what clients will do with it, so we have to make sure that validate() gets called in the client code before that. How should we do this? By warning writers of client code in our doc comments?

Here's the sort of flow I'm after:
- You set some things on an object
- You try and do something with the object
- If it fails, you look at the errors
(I disagree that it is impossible to predict what a client will do with our object, because unless they are simply treating it as a hash table, they will have to call our mthods. If they call our methods, we know what they are trying to do, and because they are our methods, we know if we need to call validate or not.)
From ouside, validate doesn't have to exist, it is just a way for the object to keep track of itself. I suppose it is a bit like an inversion of contracts, or at least an inversion of how they work in the getter/setter part of the code. Rather that having to meet a contract before you can set something on an object, you have to meet the contract before you can do anything with the object. It might just be that claim that a setter "does" something that doesn't sit well. When you are just taking in input (from a user or a client), are you "doing" anything?
From a physical perspective, yes, you are putting something in memory. In a language with strong static typing, yes, you are implicitly comparing the input with a strict type definition which you use to protect your memory, regardless of what you do, if you get the types wrong you'll have exceptions to deal with. What about a language with strong dynamic typing? Or even in PHP, with loose dynamic typing? You're not really storing the input, you're just keeping a reference to it. I'd argue that at a high level, you're not doing anything with it, because "doing" something is a cause, which implies an affect. But assigning to a variable which is loose and dynamic, nothing will ever happen beyond the assignment, there will be no external affects. With no affects, was there a cause? If there was no cause, did you really "do" anything? The only way to see if anything has even happened is to read the variable back, but by then processing has moved on to the next statement.
Regards,
Douglas
Bookmarks