1 2/* 3 +------------------------------------------------------------------------+ 4 | Phalcon Framework | 5 +------------------------------------------------------------------------+ 6 | Copyright (c) 2011-2017 Phalcon Team (https://phalconphp.com) | 7 +------------------------------------------------------------------------+ 8 | This source file is subject to the New BSD License that is bundled | 9 | with this package in the file LICENSE.txt. | 10 | | 11 | If you did not receive a copy of the license and are unable to | 12 | obtain it through the world-wide-web, please send an email | 13 | to license@phalconphp.com so we can send you a copy immediately. | 14 +------------------------------------------------------------------------+ 15 | Authors: Andres Gutierrez <andres@phalconphp.com> | 16 | Eduar Carvajal <eduar@phalconphp.com> | 17 +------------------------------------------------------------------------+ 18 */ 19 20namespace Phalcon\Mvc\Model\Validator; 21 22use Phalcon\Mvc\Model; 23use Phalcon\Mvc\EntityInterface; 24use Phalcon\Mvc\Model\Exception; 25use Phalcon\Mvc\Model\Validator; 26 27/** 28 * Phalcon\Mvc\Model\Validator\Uniqueness 29 * 30 * Validates that a field or a combination of a set of fields are not 31 * present more than once in the existing records of the related table 32 * 33 * This validator is only for use with Phalcon\Mvc\Collection. If you are using 34 * Phalcon\Mvc\Model, please use the validators provided by Phalcon\Validation. 35 * 36 *<code> 37 * use Phalcon\Mvc\Collection; 38 * use Phalcon\Mvc\Model\Validator\Uniqueness; 39 * 40 * class Subscriptors extends Collection 41 * { 42 * public function validation() 43 * { 44 * $this->validate( 45 * new Uniqueness( 46 * [ 47 * "field" => "email", 48 * "message" => "Value of field 'email' is already present in another record", 49 * ] 50 * ) 51 * ); 52 * 53 * if ($this->validationHasFailed() === true) { 54 * return false; 55 * } 56 * } 57 * } 58 *</code> 59 * 60 * @deprecated 3.1.0 61 * @see Phalcon\Validation\Validator\Uniqueness 62 */ 63class Uniqueness extends Validator 64{ 65 /** 66 * Executes the validator 67 */ 68 public function validate(<EntityInterface> record) -> boolean 69 { 70 var field, dependencyInjector, metaData, message, bindTypes, bindDataTypes, 71 columnMap, conditions, bindParams, number, composeField, columnField, 72 bindType, primaryField, attributeField, params, className, replacePairs; 73 74 let dependencyInjector = record->getDI(); 75 let metaData = dependencyInjector->getShared("modelsMetadata"); 76 77 /** 78 * PostgreSQL check if the compared constant has the same type as the 79 * column, so we make cast to the data passed to match those column types 80 */ 81 let bindTypes = []; 82 let bindDataTypes = metaData->getBindTypes(record); 83 84 if globals_get("orm.column_renaming") { 85 let columnMap = metaData->getReverseColumnMap(record); 86 } else { 87 let columnMap = null; 88 } 89 90 let conditions = []; 91 let bindParams = []; 92 let number = 0; 93 94 let field = this->getOption("field"); 95 if typeof field == "array" { 96 97 /** 98 * The field can be an array of values 99 */ 100 for composeField in field { 101 102 /** 103 * The reversed column map is used in the case to get real column name 104 */ 105 if typeof columnMap == "array" { 106 if !fetch columnField, columnMap[composeField] { 107 throw new Exception("Column '" . composeField . "' isn't part of the column map"); 108 } 109 } else { 110 let columnField = composeField; 111 } 112 113 /** 114 * Some database systems require that we pass the values using bind casting 115 */ 116 if !fetch bindType, bindDataTypes[columnField] { 117 throw new Exception("Column '" . columnField . "' isn't part of the table columns"); 118 } 119 120 /** 121 * The attribute could be "protected" so we read using "readattribute" 122 */ 123 let conditions[] = "[" . composeField . "] = ?" . number; 124 let bindParams[] = record->readAttribute(composeField); 125 let bindTypes[] = bindType; 126 127 let number++; 128 } 129 130 } else { 131 132 /** 133 * The reversed column map is used in the case to get real column name 134 */ 135 if typeof columnMap == "array" { 136 if !fetch columnField, columnMap[field] { 137 throw new Exception("Column '" . field . "' isn't part of the column map"); 138 } 139 } else { 140 let columnField = field; 141 } 142 143 /** 144 * Some database systems require that we pass the values using bind casting 145 */ 146 if !fetch bindType, bindDataTypes[columnField] { 147 throw new Exception("Column '" . columnField . "' isn't part of the table columns"); 148 } 149 150 /** 151 * We're checking the uniqueness with only one field 152 */ 153 let conditions[] = "[" . field . "] = ?0"; 154 let bindParams[] = record->readAttribute(field); 155 let bindTypes[] = bindType; 156 157 let number++; 158 } 159 160 /** 161 * If the operation is update, there must be values in the object 162 */ 163 if record->getOperationMade() == Model::OP_UPDATE { 164 165 /** 166 * We build a query with the primary key attributes 167 */ 168 if globals_get("orm.column_renaming") { 169 let columnMap = metaData->getColumnMap(record); 170 } else { 171 let columnMap = null; 172 } 173 174 for primaryField in metaData->getPrimaryKeyAttributes(record) { 175 176 if !fetch bindType, bindDataTypes[primaryField] { 177 throw new Exception("Column '" . primaryField . "' isn't part of the table columns"); 178 } 179 180 /** 181 * Rename the column if there is a column map 182 */ 183 if typeof columnMap == "array" { 184 if !fetch attributeField, columnMap[primaryField] { 185 throw new Exception("Column '" . primaryField . "' isn't part of the column map"); 186 } 187 } else { 188 let attributeField = primaryField; 189 } 190 191 /** 192 * Create a condition based on the renamed primary key 193 */ 194 let conditions[] = "[" . attributeField . "] <> ?" . number; 195 let bindParams[] = record->readAttribute(primaryField); 196 let bindTypes[] = bindType; 197 198 let number++; 199 } 200 } 201 202 /** 203 * We don't trust the user, so we pass the parameters as bound parameters 204 */ 205 let params = []; 206 let params["di"] = dependencyInjector; 207 let params["conditions"] = join(" AND ", conditions); 208 let params["bind"] = bindParams; 209 let params["bindTypes"] = bindTypes; 210 211 let className = get_class(record); 212 213 /** 214 * Check if the record does exist using a standard count 215 */ 216 if {className}::count(params) != 0 { 217 218 /** 219 * Check if the developer has defined a custom message 220 */ 221 let message = this->getOption("message"); 222 223 if typeof field == "array" { 224 let replacePairs = [":fields": join(", ", field)]; 225 if empty message { 226 let message = "Value of fields: :fields are already present in another record"; 227 } 228 } else { 229 let replacePairs = [":field": field]; 230 if empty message { 231 let message = "Value of field: ':field' is already present in another record"; 232 } 233 } 234 235 /** 236 * Append the message to the validator 237 */ 238 this->appendMessage(strtr(message, replacePairs), field, "Unique"); 239 return false; 240 } 241 242 return true; 243 } 244} 245