1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
/**
* ExistValidator validates that the attribute value exists in a table.
*
* ExistValidator checks if the value being validated can be found in the table column specified by
* the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
*
* This validator is often used to verify that a foreign key contains a value
* that can be found in the foreign table.
*
* The followings are examples of validation rules using this validator:
*
* ```php
* // a1 needs to exist
* ['a1', 'exist']
* // a1 needs to exist, but its value will use a2 to check for the existence
* ['a1', 'exist', 'targetAttribute' => 'a2']
* // a1 and a2 need to exist together, and they both will receive error message
* [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']]
* // a1 and a2 need to exist together, only a1 will receive error message
* ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']]
* // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value)
* ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ExistValidator extends Validator
{
/**
* @var string the name of the ActiveRecord class that should be used to validate the existence
* of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
* @see targetAttribute
*/
public $targetClass;
/**
* @var string|array the name of the ActiveRecord attribute that should be used to
* validate the existence of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the existence
* of multiple columns at the same time. The array values are the attributes that will be
* used to validate the existence, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
*/
public $targetAttribute;
/**
* @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value.
* This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
* on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
* is the [[\yii\db\Query|Query]] object that you can modify in the function.
*/
public $filter;
/**
* @var boolean whether to allow array type attribute.
*/
public $allowArray = false;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* @inheritdoc
*/
public function validateAttribute($model, $attribute)
{
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
if (is_array($targetAttribute)) {
if ($this->allowArray) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$params = [];
foreach ($targetAttribute as $k => $v) {
$params[$v] = is_integer($k) ? $model->$v : $model->$k;
}
} else {
$params = [$targetAttribute => $model->$attribute];
}
if (!$this->allowArray) {
foreach ($params as $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
}
}
$targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
$query = $this->createQuery($targetClass, $params);
if (is_array($model->$attribute)) {
if ($query->count("DISTINCT [[$targetAttribute]]") != count($model->$attribute)) {
$this->addError($model, $attribute, $this->message);
}
} elseif (!$query->exists()) {
$this->addError($model, $attribute, $this->message);
}
}
/**
* @inheritdoc
*/
protected function validateValue($value)
{
if ($this->targetClass === null) {
throw new InvalidConfigException('The "targetClass" property must be set.');
}
if (!is_string($this->targetAttribute)) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);
if (is_array($value)) {
if (!$this->allowArray) {
return [$this->message, []];
}
return $query->count("DISTINCT [[$this->targetAttribute]]") == count($value) ? null : [$this->message, []];
} else {
return $query->exists() ? null : [$this->message, []];
}
}
/**
* Creates a query instance with the given condition.
* @param string $targetClass the target AR class
* @param mixed $condition query condition
* @return \yii\db\ActiveQueryInterface the query instance
*/
protected function createQuery($targetClass, $condition)
{
/* @var $targetClass \yii\db\ActiveRecordInterface */
$query = $targetClass::find()->andWhere($condition);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
$query->andWhere($this->filter);
}
return $query;
}
}