This repository has been archived by the owner on Jul 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move unique_with validation rule to rinvex/laravel-support from corte…
…x/foundation
- Loading branch information
Showing
4 changed files
with
313 additions
and
4 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rinvex\Support\Validators; | ||
|
||
use Illuminate\Support\Arr; | ||
use Illuminate\Support\Str; | ||
use Illuminate\Database\Eloquent\Model; | ||
|
||
class UniqueWithRuleParser | ||
{ | ||
protected $table; | ||
|
||
protected $connection; | ||
|
||
protected $primaryField; | ||
|
||
protected $primaryValue; | ||
|
||
protected $additionalFields; | ||
|
||
protected $parsed = false; | ||
|
||
protected $ignoreColumn; | ||
|
||
protected $ignoreValue; | ||
|
||
protected $dataFields; | ||
|
||
protected $parameters; | ||
|
||
protected $attribute; | ||
|
||
protected $data; | ||
|
||
public function __construct($attribute = null, $value = null, array $parameters = [], array $data = []) | ||
{ | ||
$this->primaryField = $this->attribute = $attribute; | ||
$this->primaryValue = $value; | ||
$this->parameters = $parameters; | ||
$this->data = $data; | ||
} | ||
|
||
protected function parse() | ||
{ | ||
if ($this->parsed) { | ||
return; | ||
} | ||
$this->parsed = true; | ||
|
||
// cleaning: trim whitespace | ||
$this->parameters = array_map('trim', $this->parameters); | ||
|
||
// first item equals table name | ||
$this->table = array_shift($this->parameters); | ||
if (Str::contains($this->table, '.')) { | ||
[$this->connection, $this->table] = explode('.', $this->table, 2); | ||
} | ||
|
||
// Check if ignore data is set | ||
$this->parseIgnore(); | ||
|
||
// Parse field data | ||
$this->parseFieldData(); | ||
} | ||
|
||
protected function parseFieldData() | ||
{ | ||
$this->additionalFields = []; | ||
$this->dataFields = [$this->primaryField]; | ||
|
||
// Figure out whether field_name is the same as column_name | ||
// or column_name is explicitly specified. | ||
// | ||
// case 1: | ||
// $parameter = 'last_name' | ||
// => field_name = column_name = 'last_name' | ||
// case 2: | ||
// $parameter = 'last_name=sur_name' | ||
// => field_name = 'last_name', column_name = 'sur_name' | ||
foreach ($this->parameters as $parameter) { | ||
$parts = array_map('trim', explode('=', $parameter, 2)); | ||
$fieldName = $this->parseFieldName($parts[0]); | ||
$columnName = count($parts) > 1 ? $parts[1] : $fieldName; | ||
$this->dataFields[] = $fieldName; | ||
|
||
if ($fieldName === $this->primaryField) { | ||
$this->primaryField = $columnName; | ||
continue; | ||
} | ||
|
||
if (! Arr::has($this->data, $fieldName)) { | ||
continue; | ||
} | ||
|
||
$this->additionalFields[$columnName] = Arr::get($this->data, $fieldName); | ||
} | ||
|
||
$this->dataFields = array_values(array_unique($this->dataFields)); | ||
} | ||
|
||
public function getConnection() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->connection; | ||
} | ||
|
||
public function getTable() | ||
{ | ||
$this->parse(); | ||
$table = $this->table; | ||
|
||
if (str_contains($table, '\\') && class_exists($table) && is_a($table, Model::class, true)) { | ||
$model = new $table(); | ||
$table = $model->getTable(); | ||
|
||
return $model ? ($this->isValidationScoped($model) ? $model : $model->withoutGlobalScopes()) : (new AbstractModel())->setTable($table); | ||
} | ||
|
||
return $table; | ||
} | ||
|
||
/** | ||
* Returns whether the model validation be scoped or not. (Default: true). | ||
* | ||
* @param \Illuminate\Database\Eloquent\Model $model | ||
* | ||
* @return bool | ||
*/ | ||
protected function isValidationScoped(Model $model): bool | ||
{ | ||
return $model->isValidationScoped ?? true; | ||
} | ||
|
||
public function getPrimaryField() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->primaryField; | ||
} | ||
|
||
public function getPrimaryValue() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->primaryValue; | ||
} | ||
|
||
public function getAdditionalFields() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->additionalFields; | ||
} | ||
|
||
public function getIgnoreValue() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->ignoreValue; | ||
} | ||
|
||
public function getIgnoreColumn() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->ignoreColumn; | ||
} | ||
|
||
public function getDataFields() | ||
{ | ||
$this->parse(); | ||
|
||
return $this->dataFields; | ||
} | ||
|
||
protected function parseIgnore() | ||
{ | ||
// Ignore has to be specified as the last parameter | ||
$lastParameter = end($this->parameters); | ||
if (! $this->isIgnore($lastParameter)) { | ||
return; | ||
} | ||
|
||
$lastParameter = array_map('trim', explode('=', $lastParameter)); | ||
|
||
$this->ignoreValue = str_replace('ignore:', '', $lastParameter[0]); | ||
$this->ignoreColumn = (count($lastParameter) > 1) ? end($lastParameter) : null; | ||
|
||
// Shave of the ignore_id from the array for later processing | ||
array_pop($this->parameters); | ||
} | ||
|
||
protected function isIgnore($parameter) | ||
{ | ||
// An ignore_id can be specified by prefixing with 'ignore:' | ||
if (mb_strpos($parameter, 'ignore:') !== false) { | ||
return true; | ||
} | ||
|
||
// An ignore_id can be specified if parameter starts with a | ||
// number greater than 1 (a valid id in the database) | ||
$parts = array_map('trim', explode('=', $parameter)); | ||
|
||
return preg_match('/^[1-9][0-9]*$/', $parts[0]); | ||
} | ||
|
||
protected function parseFieldName($field) | ||
{ | ||
if (preg_match('/^\*\.|\.\*\./', $field)) { | ||
// This rule validates multiple times, because a wildcard * was used | ||
// in order to validate all elements of an array. We now need to | ||
// figure out which element we are on, so we can replace the | ||
// wildcard with the current index in the array to access the actual | ||
// data correctly. | ||
|
||
// 1. Convert main attribute (Laravel has already replaced the | ||
// wildcards with the current indizes here) to have wildcards | ||
// instead | ||
$attributeWithWildcards = preg_replace( | ||
['/^[0-9]+\./', '/\.[0-9]+\./'], | ||
['*.', '.*.'], | ||
$this->attribute | ||
); | ||
|
||
// 2. Figure out what parts of the current field string should be | ||
// replaced (Basically everything before the last wildcard) | ||
$positionOfLastWildcard = mb_strrpos($attributeWithWildcards, '*.'); | ||
$wildcardPartToBeReplaced = mb_substr($attributeWithWildcards, 0, $positionOfLastWildcard + 2); | ||
|
||
// 3. Figure out what the substitute for the replacement in the | ||
// current field string should be (Basically delete everything | ||
// after the final index part in the main attribute) | ||
$endPartToDismiss = mb_substr($attributeWithWildcards, $positionOfLastWildcard + 2); | ||
$actualIndexPartToBeSubstitute = str_replace($endPartToDismiss, '', $this->attribute); | ||
|
||
// 4. Do the actual replacement. The end result should be a string | ||
// of the current field we work on, but with the wildcards | ||
// replaced by the correct indizes for the current validation run | ||
$fieldWithActualIndizes = str_replace($wildcardPartToBeReplaced, $actualIndexPartToBeSubstitute, $field); | ||
|
||
return $fieldWithActualIndizes; | ||
} | ||
|
||
return $field; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rinvex\Support\Validators; | ||
|
||
use Illuminate\Support\Arr; | ||
use Illuminate\Support\Str; | ||
|
||
class UniqueWithValidator | ||
{ | ||
public function validateUniqueWith($attribute, $value, $parameters, $validator) | ||
{ | ||
$ruleParser = new UniqueWithRuleParser($attribute, $value, $parameters, $validator->getData()); | ||
|
||
// The presence verifier is responsible for counting rows within this | ||
// store mechanism which might be a relational database or any other | ||
// permanent data store like Redis, etc. We will use it to determine | ||
// uniqueness. | ||
$presenceVerifier = $validator->getPresenceVerifier(); | ||
if (method_exists($presenceVerifier, 'setConnection')) { | ||
$presenceVerifier->setConnection($ruleParser->getConnection()); | ||
} | ||
|
||
return $presenceVerifier->getCount($ruleParser->getTable(), $ruleParser->getPrimaryField(), $ruleParser->getPrimaryValue(), $ruleParser->getIgnoreValue(), $ruleParser->getIgnoreColumn(), $ruleParser->getAdditionalFields()) === 0; | ||
} | ||
|
||
public function replaceUniqueWith($message, $attribute, $rule, $parameters, $validator) | ||
{ | ||
$translator = $validator->getTranslator(); | ||
|
||
$ruleParser = new UniqueWithRuleParser($attribute, null, $parameters); | ||
$fields = $ruleParser->getDataFields(); | ||
|
||
if (method_exists($translator, 'trans')) { | ||
$customAttributes = $translator->trans('validation.attributes'); | ||
} else { | ||
$customAttributes = $translator->get('validation.attributes'); | ||
} | ||
|
||
// Check if translator has custom validation attributes for the fields | ||
$fields = array_map(function ($field) use ($customAttributes) { | ||
return Arr::get($customAttributes, $field) ?: str_replace('_', ' ', Str::snake($field)); | ||
}, $fields); | ||
|
||
return str_replace(':fields', implode(', ', $fields), $message); | ||
} | ||
} |