Created
March 9, 2012 01:36
-
-
Save ezzatron/2004537 to your computer and use it in GitHub Desktop.
Snippet showing basic form validation, entities, persistence etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 :)