Skip to content

Commit

Permalink
Améliore la validation (+ Refactoring modèles) (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
Donov4n authored Dec 29, 2020
1 parent f0bbab2 commit acbb5cf
Show file tree
Hide file tree
Showing 34 changed files with 384 additions and 293 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Ce projet adhère au principe du [Semantic Versioning](https://semver.org/spec/v
Ceci va, par exemple, permettre de simplifier les mises à jour de la version compilée du client (via un simple `yarn build`).
(Un lien symbolique est utilisé côté serveur pour relier les deux côtés de l'application)
- Corrige l'hôte de développement et permet sa customisation via une variable d'environnement.
- Améliorations internes de la validation des données.

## 0.10.2 (2020-11-16)

Expand Down
2 changes: 1 addition & 1 deletion server/src/App/Controllers/TokenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use Slim\Http\Request;
use Slim\Http\Response;
use Respect\Validation\Validator as V;
use Robert2\API\Validation\Validator as V;

use Robert2\API\Errors\ValidationException;
use Robert2\API\Middlewares\Security;
Expand Down
8 changes: 2 additions & 6 deletions server/src/App/Models/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@

namespace Robert2\API\Models;

use Respect\Validation\Validator as V;
use Robert2\API\Validation\Validator as V;

class Attribute extends BaseModel
{
protected $table = 'attributes';

protected $_modelName = 'Attribute';
protected $_orderField = 'id';
protected $_orderDirection = 'asc';
protected $orderField = 'id';

public function __construct(array $attributes = [])
{
Expand Down
179 changes: 102 additions & 77 deletions server/src/App/Models/BaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,21 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Database\Eloquent\Builder;
use Respect\Validation\Exceptions\NestedValidationException;
use InvalidArgumentException;
use Robert2\API\Validation\Validator;
use Robert2\API\Config\Config;
use Robert2\API\Errors;

class BaseModel extends Model
{
protected $table;
protected $_settings;
private $columns;

protected $_modelName;
protected $_orderField;
protected $_orderDirection;
protected $orderField;
protected $orderDirection;

protected $_allowedSearchFields;
protected $_searchField;
protected $_searchTerm;
protected $allowedSearchFields = [];
protected $searchField;
protected $searchTerm;

protected $fillable;

Expand All @@ -32,17 +30,12 @@ class BaseModel extends Model
'deleted_at',
];

private $_validator;

public $validation;

const EXTRA_CHARS = '-_. ÇçàÀâÂäÄåÅèÈéÉêÊëËíÍìÌîÎïÏòÒóÓôÔöÖðÐõÕøØúÚùÙûÛüÜýÝÿŸŷŶøØæÆœŒñÑßÞ';

public function __construct(array $attributes = [])
{
$this->_settings = Config::getSettings();
$this->_validator = new Validator();

Config::getCapsule();

parent::__construct($attributes);
Expand All @@ -58,7 +51,7 @@ public function getAll(bool $withDeleted = false): Builder
{
$builder = $this->_getOrderBy();

if (!empty($this->_searchTerm)) {
if (!empty($this->searchTerm)) {
$builder = $this->_setSearchConditions($builder);
}

Expand All @@ -73,7 +66,7 @@ public function getAllFiltered(array $conditions, bool $withDeleted = false): Bu
{
$builder = self::where($conditions);

if (!empty($this->_searchTerm)) {
if (!empty($this->searchTerm)) {
$builder = $this->_setSearchConditions($builder);
}

Expand All @@ -84,25 +77,57 @@ public function getAllFiltered(array $conditions, bool $withDeleted = false): Bu
return $this->_getOrderBy($builder);
}

// ------------------------------------------------------
// -
// - Setters
// -
// ------------------------------------------------------

public function setOrderBy(?string $orderBy = null, bool $ascending = true): BaseModel
{
if ($orderBy) {
$this->orderField = $orderBy;
}
$this->orderDirection = $ascending ? 'asc' : 'desc';
return $this;
}

public function setSearch(?string $term = null, $fields = null): BaseModel
{
if (empty($term)) {
return $this;
}

if ($fields) {
$fields = !is_array($fields) ? explode('|', $fields) : $fields;
foreach ($fields as $field) {
if (!in_array($field, $this->getAllowedSearchFields())) {
throw new InvalidArgumentException("Search field « $field » not allowed.");
}
$this->searchField = $field;
}
}

$this->searchTerm = trim($term);
return $this;
}

// ——————————————————————————————————————————————————————
// —
// — Setters
// — "Repository" methods
// —
// ——————————————————————————————————————————————————————

public function edit(?int $id = null, array $data = []): Model
{
if ($id && !$this->exists($id)) {
throw new Errors\NotFoundException("Edit model $this->_modelName failed, entity not found.");
throw new Errors\NotFoundException(sprintf("Edit failed, record %d not found.", $id));
}

$data = cleanEmptyFields($data);

$onlyFields = $id ? array_keys($data) : [];
$this->validate($data, $onlyFields);

try {
$model = self::updateOrCreate(['id' => $id], $data);
$model = self::firstOrNew(compact('id'));
$model->fill($data)->validate()->save();
} catch (QueryException $e) {
$error = new Errors\ValidationException();
$error->setPDOValidationException($e);
Expand All @@ -125,13 +150,13 @@ public function remove(int $id, array $options = []): ?Model

if ($model->trashed() || $options['force'] === true) {
if (!$model->forceDelete()) {
throw new \RuntimeException("Unable to destroy $this->_modelName ID #$id.");
throw new \RuntimeException(sprintf("Unable to destroy the record %d.", $id));
}
return null;
}

if (!$model->delete()) {
throw new \RuntimeException("Unable to delete $this->_modelName ID #$id.");
throw new \RuntimeException(sprintf("Unable to delete the record %d.", $id));
}

return $model;
Expand All @@ -145,39 +170,12 @@ public function unremove(int $id): Model
}

if (!$model->restore()) {
throw new \RuntimeException("Unable to restore $this->_modelName ID #$id.");
throw new \RuntimeException(sprintf("Unable to restore the record %d.", $id));
}

return $model;
}

public function setOrderBy(?string $orderBy = null, bool $ascending = true): BaseModel
{
if ($orderBy) {
$this->_orderField = $orderBy;
}
$this->_orderDirection = $ascending ? 'asc' : 'desc';
return $this;
}

public function setSearch(?string $term = null, ?string $field = null): BaseModel
{
if (empty($term)) {
return $this;
}

if ($field) {
if (!in_array($field, $this->_allowedSearchFields)) {
throw new InvalidArgumentException("Search field « $field » not allowed.");
}

$this->_searchField = $field;
}

$this->_searchTerm = trim($term);
return $this;
}

// ——————————————————————————————————————————————————————
// —
// — Other useful methods
Expand All @@ -189,34 +187,60 @@ public function exists(int $id): bool
return self::where('id', $id)->exists();
}

public function validate(array $data, array $onlyFields = []): void
public function validate(): self
{
if (empty($this->validation)) {
throw new \RuntimeException("Validation rules cannot be empty for model $this->_modelName.");
$rules = $this->validation;
if (empty($rules)) {
throw new \RuntimeException("Validation rules cannot be empty.");
}

$data = $this->getAttributes();
foreach ($data as $field => $value) {
if (is_array($value)) {
unset($data[$field]);
}
}

if (!empty($onlyFields)) {
foreach (array_keys($this->validation) as $fieldToValidate) {
if (!in_array($fieldToValidate, $onlyFields)) {
unset($this->validation[$fieldToValidate]);
unset($data[$fieldToValidate]);
}
}
// - Si le modèle existe déjà en base, on ne valide que les champs qui ont changé.
if ($this->exists) {
$rules = array_intersect_key($rules, $this->getDirty());
}

$this->_validator->validate($data, $this->validation);
// - Validation
$errors = [];
foreach ($rules as $field => $rule) {
try {
$rule->setName($field)->assert($data[$field] ?? null);
} catch (NestedValidationException $e) {
$errors[$field] = $e->getMessages();
}
}

if ($this->_validator->hasError()) {
if (count($errors) > 0) {
$ex = new Errors\ValidationException();
$ex->setValidationErrors($this->_validator->getErrors());
$ex->setValidationErrors($errors);
throw $ex;
}

return $this;
}

public function getTableColumns(): array
{
if (!$this->columns) {
$this->columns = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
}
return $this->columns;
}

public function getAllowedSearchFields(): array
{
return array_unique(array_merge(
(array)$this->searchField,
(array)$this->allowedSearchFields
));
}

// ------------------------------------------------------
Expand All @@ -227,8 +251,12 @@ public function validate(array $data, array $onlyFields = []): void

protected function _getOrderBy(?Builder $builder = null): Builder
{
$order = $this->_orderField ?: 'id';
$direction = $this->_orderDirection ?: 'asc';
$direction = $this->orderDirection ?: 'asc';

$order = $this->orderField;
if (!$order) {
$order = in_array('name', $this->getTableColumns()) ? 'name' : 'id';
}

if ($builder) {
return $builder->orderBy($order, $direction);
Expand All @@ -239,24 +267,21 @@ protected function _getOrderBy(?Builder $builder = null): Builder

protected function _setSearchConditions(Builder $builder): Builder
{
if (!$this->_searchField || !$this->_searchTerm) {
if (!$this->searchField || !$this->searchTerm) {
return $builder;
}

$term = sprintf('%%%s%%', addcslashes($this->_searchTerm, '%_'));
$term = sprintf('%%%s%%', addcslashes($this->searchTerm, '%_'));

if (preg_match('/\|/', $this->_searchField)) {
$fields = explode('|', $this->_searchField);

$group = function (Builder $query) use ($fields, $term) {
foreach ($fields as $field) {
if (is_array($this->searchField)) {
$group = function (Builder $query) use ($term) {
foreach ($this->searchField as $field) {
$query->orWhere($field, 'LIKE', $term);
}
};

return $builder->where($group);
}

return $builder->where($this->_searchField, 'LIKE', $term);
return $builder->where($this->searchField, 'LIKE', $term);
}
}
17 changes: 7 additions & 10 deletions server/src/App/Models/Bill.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Respect\Validation\Validator as V;
use Robert2\API\Validation\Validator as V;

use Robert2\API\Errors;
use Robert2\API\Config\Config;
Expand All @@ -18,14 +18,11 @@ class Bill extends BaseModel
use SoftDeletes;
use WithPdf;

protected $table = 'bills';
protected $orderField = 'date';
protected $orderDirection = 'desc';

protected $_modelName = 'Bill';
protected $_orderField = 'date';
protected $_orderDirection = 'desc';

protected $_allowedSearchFields = ['number', 'due_amount', 'date'];
protected $_searchField = 'number';
protected $allowedSearchFields = ['number', 'due_amount', 'date'];
protected $searchField = 'number';

public function __construct(array $attributes = [])
{
Expand Down Expand Up @@ -152,15 +149,15 @@ public function getPdfName(int $id): string
{
$model = $this->withTrashed()->find($id);
if (!$model) {
throw new NotFoundException(sprintf('%s not found.', $this->_modelName));
throw new NotFoundException(sprintf('Record %d not found.', $id));
}

$company = Config::getSettings('companyData');

$i18n = new I18n(Config::getSettings('defaultLang'));
$fileName = sprintf(
'%s-%s-%s-%s.pdf',
$i18n->translate($this->_modelName),
$i18n->translate('Bill'),
slugify($company['name']),
$model->number,
slugify($model->Beneficiary->full_name)
Expand Down
Loading

0 comments on commit acbb5cf

Please sign in to comment.