Extract model validation into a trait

Also use Laravel’s ValidationException rather than our own custom one
This commit is contained in:
Toby Zerner 2015-07-05 12:25:08 +09:30
parent 04501545e3
commit 53e269fd89
11 changed files with 193 additions and 234 deletions

View File

@ -1,6 +1,7 @@
<?php namespace Flarum\Api\Actions;
use Flarum\Api\Request;
use Illuminate\Contracts\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Flarum\Core\Exceptions\ValidationFailureException;
use Flarum\Core\Exceptions\PermissionDeniedException;
@ -20,9 +21,9 @@ abstract class JsonApiAction implements ActionInterface
// TODO: Move this error handling code to middleware?
try {
return $this->respond($request);
} catch (ValidationFailureException $e) {
} catch (ValidationException $e) {
$errors = [];
foreach ($e->getErrors()->getMessages() as $field => $messages) {
foreach ($e->errors()->toArray() as $field => $messages) {
$errors[] = [
'detail' => implode("\n", $messages),
'path' => $field

View File

@ -19,8 +19,6 @@ class CoreServiceProvider extends ServiceProvider
return get_class($command).'Handler@handle';
});
Model::setValidator($this->app['validator']);
Forum::allow('*', function (Forum $forum, User $user, $action) {
return $user->hasPermission('forum.'.$action) ?: null;
});

View File

@ -12,19 +12,28 @@ use Flarum\Core\Users\User;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\ValidatesBeforeSave;
class Discussion extends Model
{
use EventGenerator;
use Locked;
use VisibleScope;
use ValidatesBeforeSave;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'discussions';
/**
* The validation rules for this model.
*
* @var array
*/
public static $rules = [
protected $rules = [
'title' => 'required',
'start_time' => 'required|date',
'comments_count' => 'integer',
@ -37,13 +46,6 @@ class Discussion extends Model
'last_post_number' => 'integer'
];
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'discussions';
/**
* The attributes that should be mutated to dates.
*

View File

@ -19,6 +19,8 @@ class DiscussionsServiceProvider extends ServiceProvider
new Extend\EventSubscriber('Flarum\Core\Discussions\Listeners\DiscussionMetadataUpdater')
]);
Discussion::setValidator($this->app->make('validator'));
Discussion::allow('*', function (Discussion $discussion, User $user, $action) {
return $user->hasPermission('discussion.'.$action) ?: null;
});

View File

@ -1,68 +0,0 @@
<?php namespace Flarum\Core\Exceptions;
use Exception;
use Illuminate\Support\MessageBag;
use DomainException;
class ValidationFailureException extends DomainException
{
/**
* @var MessageBag
*/
protected $errors;
/**
* @var array
*/
protected $input = array();
/**
* @param string $message
* @param int $code
* @param Exception $previous
*/
public function __construct($message = '', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->errors = new MessageBag;
}
/**
* @param MessageBag $errors
* @return $this
*/
public function setErrors(MessageBag $errors)
{
$this->errors = $errors;
return $this;
}
/**
* @return MessageBag
*/
public function getErrors()
{
return $this->errors;
}
/**
* @param array $input
* @return $this
*/
public function setInput(array $input)
{
$this->input = $input;
return $this;
}
/**
* @return array
*/
public function getInput()
{
return $this->input;
}
}

View File

@ -35,13 +35,6 @@ abstract class Model extends Eloquent
*/
protected static $dateAttributes = [];
/**
* The validation rules for this model.
*
* @var array
*/
public static $rules = [];
/**
* An array of custom relation methods, grouped by subclass.
*
@ -49,124 +42,6 @@ abstract class Model extends Eloquent
*/
protected static $relationMethods = [];
/**
* The validation factory instance.
*
* @var \Illuminate\Contracts\Validation\Factory
*/
protected static $validator;
/**
* Boot the model.
*
* @return void
*/
public static function boot()
{
parent::boot();
// Before the model is saved, validate it. If validation fails, an
// exception will be thrown, preventing the model from saving.
static::saving(function ($model) {
$model->assertValid();
});
}
/**
* Set the validation factory instance.
*
* @param \Illuminate\Contracts\Validation\Factory $validator
*/
public static function setValidator(Factory $validator)
{
static::$validator = $validator;
}
/**
* Check whether the model is valid in its current state.
*
* @return boolean
*/
public function valid()
{
return $this->makeValidator()->passes();
}
/**
* Throw an exception if the model is not valid in its current state.
*
* @return void
*
* @throws \Flarum\Core\ValidationFailureException
*/
public function assertValid()
{
$validator = $this->makeValidator();
if ($validator->fails()) {
$this->throwValidationFailureException($validator);
}
}
protected function throwValidationFailureException($validator)
{
throw (new ValidationFailureException)
->setErrors($validator->errors())
->setInput($validator->getData());
}
/**
* Make a new validator instance for this model.
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function makeValidator()
{
$dirty = $this->getDirty();
$rules = $this->expandUniqueRules(array_only(static::$rules, array_keys($dirty)));
// TODO: translation
$messages = [
'unique' => 'That :attribute has already been taken.',
'email' => 'The :attribute must be a valid email address.',
'alpha_num' => 'The :attribute may only contain letters and numbers.'
];
return static::$validator->make($dirty, $rules, $messages);
}
/**
* Expand 'unique' rules in a set of validation rules into a fuller form
* that Laravel's validator can understand.
*
* @param array $rules
* @return array
*/
protected function expandUniqueRules($rules)
{
foreach ($rules as $column => &$ruleset) {
if (is_string($ruleset)) {
$ruleset = explode('|', $ruleset);
}
foreach ($ruleset as &$rule) {
if (strpos($rule, 'unique') === 0) {
$parts = explode(':', $rule);
$key = $this->getKey() ?: 'NULL';
$rule = 'unique:'.$this->getTable().','.$column.','.$key.','.$this->getKeyName();
if (! empty($parts[1])) {
$wheres = explode(',', $parts[1]);
foreach ($wheres as &$where) {
$where .= ','.$this->$where;
}
$rule .= ','.implode(',', $wheres);
}
}
}
}
return $rules;
}
/**
* Get the attributes that should be converted to dates.
*
@ -216,7 +91,7 @@ abstract class Model extends Eloquent
*
* @throws \LogicException
*/
public function getCustomRelationship($name)
protected function getCustomRelationship($name)
{
$relation = static::$relationMethods[get_called_class()][$name]($this);
@ -225,7 +100,7 @@ abstract class Model extends Eloquent
. 'Illuminate\Database\Eloquent\Relations\Relation');
}
return $this->relations[$method] = $relation->getResults();
return $this->relations[$name] = $relation->getResults();
}
/**

View File

@ -4,21 +4,27 @@ use DomainException;
use Flarum\Core\Posts\Events\PostWasDeleted;
use Flarum\Core\Model;
use Flarum\Core\Support\Locked;
use Flarum\Core\Exceptions\ValidationFailureException;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\ValidatesBeforeSave;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
use EventGenerator;
use Locked;
use ValidatesBeforeSave;
/**
* {@inheritdoc}
*/
protected $table = 'posts';
/**
* The validation rules for this model.
*
* @var array
*/
public static $rules = [
protected $rules = [
'discussion_id' => 'required|integer',
'time' => 'required|date',
'content' => 'required',
@ -30,11 +36,6 @@ class Post extends Model
'hide_user_id' => 'integer',
];
/**
* {@inheritdoc}
*/
protected $table = 'posts';
/**
* {@inheritdoc}
*/

View File

@ -19,6 +19,8 @@ class PostsServiceProvider extends ServiceProvider
new Extend\PostType('Flarum\Core\Posts\DiscussionRenamedPost')
]);
Post::setValidator($this->app->make('validator'));
CommentPost::setFormatter($this->app->make('flarum.formatter'));
Post::allow('*', function ($post, $user, $action) {

View File

@ -0,0 +1,143 @@
<?php namespace Flarum\Core\Support;
use Illuminate\Contracts\Validation\Factory;
use Illuminate\Contracts\Validation\ValidationException;
use Illuminate\Validation\Validator;
trait ValidatesBeforeSave
{
/**
* The validation factory instance.
*
* @var Factory
*/
protected static $validator;
/**
* Boot the model.
*
* @return void
*/
public static function bootValidatesBeforeSave()
{
// Before the model is saved, validate it. If validation fails, an
// exception will be thrown, preventing the model from saving.
static::saving(function ($model) {
$model->assertValid();
});
}
/**
* Set the validation factory instance.
*
* @param Factory $validator
*/
public static function setValidator(Factory $validator)
{
static::$validator = $validator;
}
/**
* Check whether the model is valid in its current state.
*
* @return boolean
*/
public function valid()
{
return $this->makeValidator()->passes();
}
/**
* Throw an exception if the model is not valid in its current state.
*
* @return void
*
* @throws ValidationException
*/
public function assertValid()
{
$validator = $this->makeValidator();
if ($validator->fails()) {
$this->throwValidationException($validator);
}
}
/**
* @param Validator $validator
* @throws ValidationException
*/
protected function throwValidationException(Validator $validator)
{
throw new ValidationException($validator);
}
/**
* Make a new validator instance for this model.
*
* @return \Illuminate\Validation\Validator
*/
protected function makeValidator()
{
$dirty = $this->getDirty();
$rules = $this->expandUniqueRules(array_only($this->rules, array_keys($dirty)));
$validator = static::$validator->make($dirty, $rules);
// TODO: event
return $validator;
}
/**
* Expand 'unique' rules in a set of validation rules into a fuller form
* that Laravel's validator can understand.
*
* @param array $rules
* @return array
*/
protected function expandUniqueRules($rules)
{
foreach ($rules as $attribute => &$ruleset) {
if (is_string($ruleset)) {
$ruleset = explode('|', $ruleset);
}
foreach ($ruleset as &$rule) {
if (strpos($rule, 'unique') === 0) {
$rule = $this->expandUniqueRule($attribute, $rule);
}
}
}
return $rules;
}
/**
* Expand a 'unique' rule into a fuller form that Laravel's validator can
* understand, based on this model's properties.
*
* @param string $attribute
* @param string $rule
* @return string
*/
protected function expandUniqueRule($attribute, $rule)
{
$parts = explode(':', $rule);
$key = $this->getKey() ?: 'NULL';
$rule = 'unique:'.$this->getTable().','.$attribute.','.$key.','.$this->getKeyName();
if (! empty($parts[1])) {
$wheres = explode(',', $parts[1]);
foreach ($wheres as &$where) {
$where .= ','.$this->$where;
}
$rule .= ','.implode(',', $wheres);
}
return $rule;
}
}

View File

@ -17,18 +17,35 @@ use Flarum\Core\Users\Events\UserEmailChangeWasRequested;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\ValidatesBeforeSave;
class User extends Model
{
use EventGenerator;
use Locked;
use VisibleScope;
use ValidatesBeforeSave;
/**
* {@inheritdoc}
*/
protected $table = 'users';
/**
* The validation rules for this model.
*
* @var array
*/
protected $rules = [
'username' => 'required|alpha_dash|unique',
'email' => 'required|email|unique',
'password' => 'required',
'join_time' => 'date',
'last_seen_time' => 'date',
'discussions_count' => 'integer',
'posts_count' => 'integer',
];
/**
* {@inheritdoc}
*/
@ -46,21 +63,6 @@ class User extends Model
*/
protected static $formatter;
/**
* The validation rules for this model.
*
* @var array
*/
public static $rules = [
'username' => 'required|alpha_dash|unique',
'email' => 'required|email|unique',
'password' => 'required',
'join_time' => 'date',
'last_seen_time' => 'date',
'discussions_count' => 'integer',
'posts_count' => 'integer',
];
/**
* The hasher with which to hash passwords.
*
@ -158,13 +160,13 @@ class User extends Model
public function requestEmailChange($email)
{
if ($email !== $this->email) {
$validator = static::$validator->make(
compact('email'),
$this->expandUniqueRules(array_only(static::$rules, 'email'))
);
$validator = $this->makeValidator();
$validator->setRules(array_only($validator->getRules(), 'email'));
$validator->setData(compact('email'));
if ($validator->fails()) {
$this->throwValidationFailureException($validator);
$this->throwValidationException($validator);
}
$this->raise(new UserEmailChangeWasRequested($this, $email));

View File

@ -21,6 +21,7 @@ class UsersServiceProvider extends ServiceProvider
User::setHasher($this->app->make('hash'));
User::setFormatter($this->app->make('flarum.formatter'));
User::setValidator($this->app->make('validator'));
User::addPreference('discloseOnline', 'boolval', true);
User::addPreference('indexProfile', 'boolval', true);