HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Validating a model that requires database access

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
requiresvalidatingdatabasethatmodelaccess

Problem

Let's say I have this User model:

class User
{
    public $id;
    public $email;
    public $password;
    public $errors;

    public function isValid()
    {
        if (strpos($this->email, '@') === false) {
            $this->errors['email'] = 'Please enter an email address';
        }
        if (!$this->password) {
            $this->errors['password'] = 'Please enter a password';
        } elseif (strlen($this->password) errors['password'] = 'Please enter a longer password';
        }

        return !$this->errors;
    }
}


And let's say I have this UserDAO for retrieving, adding, updating, and deleting users:

```
class UserDAO
{
protected $conn;
protected $logger;

public function __construct(PDO $dbh, Logger $logger)
{
$this->dbh = $dbh;
$this->logger = $logger;
}

public function getUsers()
{
$rows = null;
try {
$rows = $this->dbh->query("SELECT * FROM users")->fetchAll();
} catch (PDOException $e) {
$this->logger->log($e->getMessage(), __METHOD__);
}
return $rows;
}

public function getUserById($id)
{
$row = null;
try {
$sth = $this->dbh->prepare("SELECT * FROM users WHERE id = ?");
$sth->execute(array($id));
$row = $sth->fetchObject('User');
} catch (PDOException $e) {
$this->logger->log($e->getMessage(), __METHOD__);
}
return $row;
}

public function addUser(User &$user)
{
$success = false;
try {
$sth = $this->dbh->prepare("
INSERT INTO users (email, password) VALUES (?, ?)
");
$sth->execute(array($user->email, $user->password));
if ($success = (bool) $sth->rowCount()) {
$user->id = $this->dbh->lastInsertId();
}
} catch (PDOException $e) {
$this->logger->log($e->getMessage(), __METHOD__);

Solution

Best approach would be to have another Object to do the validation based on specific configuration.

Your User should NOT be responsible for validating itself, neither should the DAO.
Validation is a specific task and should be handled by a specific Class.

Example of this approach would be something like this (omitiong interfaces for brevity):

validation class

Class Validator
{
protected $errors = [];

public function __construct(DaoInterface $daoDependency, $otherDependency)
{
//...
}

public function validate($object, array $rules)
{
foreach ($rules as $rule)
{
$propertyName = $rule['property'];
if (!$this->isValidateProperty($object, $propertyName, $rule['config'])) {
$this->errors[$object->$propertyName] = 'is invalid ' . $rule['message'];
}
}
}

public function isValid()
{
return (bool)count($this->errors);
}

public function getErrors()
{
return $this->errors;
}

protected function isValidProperty($object, $propertyName, $ruleConfig)
{
// $ruleConfig should have information about what validation you want to use and relevant validation should be executed base on it (it can separate objects etc)
if (!property_exists($object, $propertyName)
throw new MissingPropertyException();

if (isset($ruleConfig['email-unique-in-db') {
if ($daoDependecny->emailExists($object->$propertyName) {
return false;
}
}
if (isset($ruleConfig['email-is-valid') {
return (bool)strpos($object->$propertyName, '@');
}
// ... etc but better with objects for each validation type.
}
}


usage

$userDAO = new UserDAO($dbh, $logger);
$user = new User();
$user->email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$user->password = filter_input(INPUT_POST, 'password');

// get your validation rules
// you can have them stored in other object, plain array, yml whatever you like. for instance $rules = new UserConstrains(); or
$rules['config] = [
'email' => [
'email-unique-in-db',
'email-is-valid'
],
'otherfield' => [
'callback' => ['someCallbackFunction']
]
];

// create validator
$validator = new Validator($userDao, $otherDepenency);
$validator->validate($user, $rules);
// validate user

if (validator->isValid()) {
// .. do stuff
} else {
// do something with $user->errors
}


The separation of concerns is the most important thing here.
You can extend/decorate the validator class for specific use cases

Also if would recommend using some specialized validation library for this task for instance symfony2 validation component can be used stand alone: http://symfony.com/doc/current/book/validation.html

Context

StackExchange Code Review Q#119974, answer score: 2

Revisions (0)

No revisions yet.