Skip to content

Instantly share code, notes, and snippets.

@ezzatron
Created March 9, 2012 01:36
Show Gist options
  • Save ezzatron/2004537 to your computer and use it in GitHub Desktop.
Save ezzatron/2004537 to your computer and use it in GitHub Desktop.
Snippet showing basic form validation, entities, persistence etc.
<?php
// VALIDATORS
/**
* Stores validation error messages.
*/
class ErrorData
{
/**
* @param string $message
*/
public function add($message)
{
$this->messages[] = $message;
}
/**
* @return array<string>
*/
public function messages()
{
return $this->messages;
}
/**
* @var array<string>
*/
protected $messages = array();
}
/**
* Stores groups of ErrorData
*/
class ErrorCollection
{
/**
* @param string $name
* @param ErrorData $errors
*/
public function set($name, ErrorData $errors)
{
$this->errors[$name] = $errors;
}
/**
* @param string $name
*
* @return ErrorData
*/
public function errors($name)
{
if (!$this->hasErrors($name))
{
throw new UndefinedErrorDataException($name);
}
return $this->errors[$name];
}
/**
* @param string $name
*
* @return boolean
*/
public function hasErrors($name)
{
return array_key_exists($name, $this->errors);
}
/**
* @var array<ErrorData>
*/
protected $errors = array();
}
/**
* Anything that extends this can validate input.
*/
abstract class Validator
{
/**
* @param mixed $value
* @param ErrorData $errorData
*
* @return boolean
*/
abstract public function check($value, ErrorData $errorData);
/**
* @param type $value
*
* @throws ValidationException
*/
public function assert($value)
{
$errorData = new ErrorData;
if (!$this->check($value, $errorData))
{
foreach ($errorData->messages() as $errorMessage)
{
throw new ValidationException($errorMessage);
}
throw new ValidationException('Unknown validation error.');
}
}
}
class ValidationException extends Exception
{
/**
* @param string $message
* @param Exception $previous
*/
public function __construct($message, Exception $previous = NULL)
{
parent::__construct($message, 0, $previous);
}
}
/**
* Example validator for username
*/
class UsernameValidator extends Validator
{
/**
* @param mixed $value
* @param ErrorData $errorData
*
* @return boolean
*/
public function check($value, ErrorData $errorData)
{
$valid = false;
if (!preg_match('/[^\w]/', $value))
{
$valid = true;
}
else
{
$errorData->add('Usernames cannot contain invalid characters.');
}
return $valid;
}
}
/**
* This is just for example's sake.
*/
class SignupForm
{
public function __construct()
{
$this->validators['username'] = new UsernameValidator;
// ...
}
public function fields()
{
return array(
'username',
// ...
);
}
/**
* @param array<string,string> $input
* @param ErrorCollection $errors
*/
public function validate(array $input, ErrorCollection $errors)
{
foreach ($this->validators as $name => $validator)
{
$errorData = new ErrorData;
if (!array_key_exists($name, $input))
{
// no data provided by user
$errorData->add("'".$name."' is mandatory.");
$errors->set($name, $errorData);
continue;
}
if ($validator->check($input[$name], $errorData))
{
// validation passed
continue;
}
// validation failed
$errors->set($name, $errorData);
}
}
/**
* @var array<Validator>
*/
protected $validators = array();
}
// EXAMPLE REQUEST HANDLING
if ('POST' == $_SERVER['REQUEST_METHOD'])
{
$form = new SignupForm;
$errors = new ErrorCollection;
if ($form->validate($_POST, $errors))
{
// validation passed
$user = new User;
$user->setUsername($_POST['username']);
// ...
$user->store();
$redirectUrl = '/users/' . urlencode($user->id());
header('Location: '.$redirectUrl);
exit;
}
// validation failed
// this should be done in a template
echo '<p>There were problems with your submission:</p>';
echo '<ul class="form-errors">';
foreach ($form->fields() as $name)
{
echo '<li>';
echo htmlspecialchars($name);
echo ':';
echo '<ul>';
foreach ($errors->errors($name) as $fieldError)
{
echo '<li>';
echo htmlspecialchars($fieldError);
echo '</li>';
}
echo '</ul>';
echo '</li>';
}
echo '</ul>';
}
// display form
// USER ENTITY
interface Entity
{
/**
* @return string
*/
public function table();
/**
* @return array<string,callback>
*/
public function fields();
}
class User implements Entity
{
public function __construct()
{
$this->usernameValidator = new UsernameValidator;
}
/**
* @return string
*/
public function table()
{
return 'user';
}
/**
* @return array<string,callback>
*/
public function fields()
{
return array(
'username' => array($this, 'username'),
);
}
public function setUsername($username)
{
try
{
$this->usernameValidator->assert($username);
}
catch (ValidationException $e)
{
throw new InvalidUsernameException($e);
}
// if we get here, it means the username is valid
$this->username = $username;
}
/**
* @return string
*/
public function username()
{
return $this->username;
}
// ...
/**
* @var Validator
*/
protected $usernameValidator;
/**
* @var string
*/
protected $username;
// ...
}
// PERSISTENCE - very basic example
class EntityManager
{
public function persist(Entity $entity)
{
// I haven't covered UPDATE here
$statement = $this->pdo()->prepare(
'INSERT INTO '
. $this->escapeIdentifier($entity->table())
. ' ('
. $this->buildFieldList($entity)
. ') VALUES ('
. $this->buildParameters($entity)
. ')'
);
$result = false;
try
{
$result = $statement->execute($this->statementArguments($entity));
} catch (PDOException $e) {
throw new PersistenceException("Unable to persist entity of class '".get_class($entity)."'.", $e);
}
if (!$result)
{
throw new PersistenceException("Unable to persist entity of class '".get_class($entity)."'.");
}
}
/**
* @param Entity $entity
*
* @return string
*/
protected function buildFieldList(Entity $entity)
{
$fields = array();
foreach ($entity->fields() as $name => $callback)
{
$fields[] = $this->escapeIdentifier($name);
}
return implode(', ', $fields);
}
/**
* @param Entity $entity
*
* @return string
*/
protected function buildParameters(Entity $entity)
{
$arguments = array();
foreach ($entity->fields() as $name => $callback)
{
$arguments[] = ':'.$name;
}
return implode(', ', $arguments);
}
/**
* @param Entity $entity
*
* @return array
*/
protected function statementArguments(Entity $entity)
{
$arguments = array();
foreach ($entity->fields() as $name => $callback)
{
$arguments[':'.$name] = $callback();
}
return $arguments;
}
/**
* @param string $identifier
*
* @return $identifier
*/
protected function escapeIdentifier($identifier)
{
return '`'.str_replace('`', '``', $identifier).'`';
}
/*
* @return PDO
*/
protected function pdo()
{
if (null === $this->pdo)
{
try
{
$this->pdo = new PDO('mysql://localhost');
}
catch (PDOException $e)
{
throw new PersistenceException("Unable to connect to database.", $e);
}
}
return $this->pdo;
}
/**
* @var PDO
*/
protected $pdo;
}
@jamezpolley
Copy link

So much whitespace! Reminds me why I like python...

@ezzatron
Copy link
Author

Jeez the tab size is ridiculous. It doesn't look anything like that in any sane IDE. I don't use tabs outside of work anyway, and this is one of the reasons why :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment