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: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371:
<?php
/**
* MvcCore
*
* This source file is subject to the BSD 3 License
* For the full copyright and license information, please view
* the LICENSE.md file that are distributed with this source code.
*
* @copyright Copyright (c) 2016 Tom Flidr (https://github.com/mvccore)
* @license https://mvccore.github.io/docs/mvccore/5.0.0/LICENCE.md
*/
namespace MvcCore\Ext\Forms;
/**
* Responsibility: init, pre-dispatch and render common form control,
* it could be `input`, `select` or textarea. This
* class is not possible to instantiate, you need to
* extend this class to create own specific form control.
*/
abstract class Field
implements \MvcCore\Ext\Forms\IField {
use \MvcCore\Ext\Forms\Field\Props;
use \MvcCore\Ext\Forms\Field\Getters;
use \MvcCore\Ext\Forms\Field\Setters;
use \MvcCore\Ext\Forms\Field\Rendering;
/**
* Create new form control instance.
* @param array $cfg Config array with public properties and it's
* values which you want to configure, presented
* in camel case properties names syntax.
* @throws \InvalidArgumentException
* @return \MvcCore\Ext\Forms\Field
*/
public static function CreateInstance ($cfg = []) {
return new static($cfg);
}
/**
* Create new form control instance.
* @param array $cfg Config array with public properties and it's
* values which you want to configure, presented
* in camel case properties names syntax.
* @throws \InvalidArgumentException
* @return void
*/
public function __construct ($cfg = []) {
static::$templates = (object) static::$templates;
foreach ($cfg as $propertyName => $propertyValue) {
if (in_array($propertyName, static::$declaredProtectedProperties)) {
$this->throwNewInvalidArgumentException(
'Property `'.$propertyName.'` is not possible '
.'to configure by constructor `$cfg` param.'
);
} else {
$this->{$propertyName} = $propertyValue;
}
}
$validators = is_string($this->validators)
? [$this->validators]
: (is_array($this->validators)
? $this->validators
: [$this->validators]);
call_user_func([$this, 'SetValidators'], $validators);
}
/**
* Sets any custom property `"propertyName"` by `\MvcCore\Ext\Forms\Field::SetPropertyName("value");`,
* which is not necessary to define previously or gets previously defined
* property `"propertyName"` by `\MvcCore\Ext\Forms\Field::GetPropertyName();`.
* Throws exception if no property defined by get call or if virtual call
* begins with anything different from `Set` or `Get`.
* This method returns custom value for get and `\MvcCore\Request` instance for set.
* @param string $name
* @param array $arguments
* @throws \InvalidArgumentException
* @return mixed|\MvcCore\Ext\Forms\Field
*/
public function __call ($name, $arguments = []) {
$nameBegin = strtolower(substr($name, 0, 3));
$prop = lcfirst(substr($name, 3));
if ($nameBegin == 'get') {
if (property_exists($this, $prop)) {
return $this->{$prop};
} else {
return $this->throwNewInvalidArgumentException("No property with name '$prop' defined.");
}
} else if ($nameBegin == 'set') {
$this->{$prop} = isset($arguments[0]) ? $arguments[0] : NULL;
return $this;
} else {
return $this->throwNewInvalidArgumentException("No method with name '$name' defined.");
}
}
/**
* Universal getter, if property not defined - `NULL` is returned.
* @param string $name
* @return mixed
*/
public function __get ($name) {
return isset($this->$name) ? $this->$name : NULL ;
}
/**
* Universal setter, if property not defined - it's automatically declared.
* @param string $name
* @param mixed $value
* @return \MvcCore\Ext\Forms\Field
*/
public function __set ($name, $value) {
$this->$name = $value;
return $this;
}
/**
* This INTERNAL method is called from `\MvcCore\Ext\Form` after field
* is added into form instance by `$form->AddField();` method. Do not
* use this method even if you don't develop any form field.
* - Check if field has any name, which is required.
* - Set up form and field id attribute by form id and field name.
* - Set up required.
* - Set up translate boolean property.
* @param \MvcCore\Ext\Form $form
* @throws \InvalidArgumentException
* @return \MvcCore\Ext\Forms\Field
*/
public function SetForm (\MvcCore\Ext\IForm $form) {
if (!$this->name) $this->throwNewInvalidArgumentException(
'No `name` property defined.'
);
$this->form = $form;
if ($this->id === NULL)
$this->id = implode(\MvcCore\Ext\IForm::HTML_IDS_DELIMITER, [
$form->GetId(),
$this->name
]);
// if there is no specific required boolean - set required boolean by form
if ($this instanceof \MvcCore\Ext\Forms\Fields\IVisibleField)
$this->required = $this->required === NULL
? $form->GetDefaultRequired()
: $this->required ;
if ($this->translate === NULL)
$this->translate = $form->GetTranslate();
return $this;
}
/**
* This INTERNAL method is called from `\MvcCore\Ext\Form` just before
* field is naturally rendered. It sets up field for rendering process.
* Do not use this method even if you don't develop any form field.
* - Set up field render mode if not defined.
* - Translate label text if necessary.
* @return void
*/
public function PreDispatch () {
$form = $this->form;
// if there is no specific render mode - set render mode by form
if ($this instanceof \MvcCore\Ext\Forms\Fields\IVisibleField && $this->renderMode === NULL)
$this->renderMode = $form->GetDefaultFieldsRenderMode();
if ($this->translate) {
if ($this->translateTitle && $this->title !== NULL)
$this->title = $form->Translate($this->title);
if ($this instanceof \MvcCore\Ext\Forms\Fields\ILabel && $this->translateLabel && $this->label !== NULL)
$this->label = $form->Translate($this->label);
}
}
/**
* This INTERNAL method is called from `\MvcCore\Ext\Form`
* in submit processing. Do not use this method even if you
* don't develop form library or any form field.
*
* Submit field value - process raw request value with all
* configured validators and add errors into form if necessary.
* Then return safe processed value by all from validators or `NULL`.
*
* @param array $rawRequestParams Raw request params from MvcCore
* request object based on raw app
* input, `$_GET` or `$_POST`.
* @return string|int|array|NULL
*/
public function Submit (array & $rawRequestParams = []) {
$result = NULL;
$fieldName = $this->name;
if ($this instanceof \MvcCore\Ext\Forms\Fields\IVisibleField && ($this->readOnly || $this->disabled)) {
// get value previously assigned from session or by developer when called:
// `$form->SetValues(array(/* some predefined values from DB...*/))`
$result = $this->value;
} else {
$result = NULL;
if (isset($rawRequestParams[$fieldName]))
$result = $rawRequestParams[$fieldName];
$alwaysValidate = $this instanceof \MvcCore\Ext\Forms\Fields\IAlwaysValidate;
if ($result === NULL && !$alwaysValidate) {
$processValidators = FALSE;
} else {
$processValidators = TRUE;
}
if ($processValidators && $this->validators) {
foreach ($this->validators as $validatorName => $validatorNameOrInstance) {
// set safe value as field submit result value
$validator = NULL;
if (is_string($validatorNameOrInstance)) {
$validator = $this->form->GetValidator($validatorName);
} else if ($validatorNameOrInstance instanceof \MvcCore\Ext\Forms\IValidator) {
$validator = $validatorNameOrInstance
->SetForm($this->form)
->SetField($this);
} else {
return $this->throwNewInvalidArgumentException(
'Unknown validator type configured: `' . $validatorNameOrInstance
. '`, type: `' . gettype($validatorNameOrInstance) . '`.'
);
}
$result = $validator
->SetField($this)
->Validate($result);
}
}
// add required error message if necessary and if there are no other errors
if ($this->required && !$this->errors)
if (
$result === NULL || // normally validator return NULL on failure
(is_string($result) && mb_strlen($result) === 0) || // but line is for sure
(is_array($result) && count($result) === 0) // and this line is for sure
)
$this->AddValidationError(
$this->form->GetDefaultErrorMsg(\MvcCore\Ext\Forms\IError::REQUIRED)
);
}
return $result;
}
/**
* Default implementation for any extended field class to get field specific
* data for validator purposes. If you want to extend any field, you could
* implement this method better and faster. It's only necessary in your
* implementation to return array with keys to be field specific properties
* in camel case and values to be field properties values, which validator
* requires.
* @param array $fieldPropsDefaultValidValues
* @return array
*/
public function & GetValidatorData ($fieldPropsDefaultValidValues = []) {
$result = [];
foreach ($fieldPropsDefaultValidValues as $propName => $defaultValidatorValue)
if (property_exists($this, $propName))
$result[$propName] = $this->{$propName};
return $result;
}
/**
* This INTERNAL method is called from `\MvcCore\Ext\Forms\Field`
* in submit processing. Do not use this method even if you
* don't develop any form field or field validator.
*
* Add form error with given error message containing
* possible replacements for array values.
*
* If there is necessary to translate form elements
* (form has configured property `translator` as `callable`)
* than given error message is translated first before replacing.
*
* Before error message processing for replacements,
* there is automatically assigned into first position into `$errorMsgArgs`
* array (translated) field label or field name and than
* error message is processed for replacements.
*
* If there is given some custom `$replacingCallable` param,
* error message is processed for replacements by custom `$replacingCallable`.
*
* If there is not given any custom `$replacingCallable` param,
* error message is processed for replacements by static `Format()`
* method by configured form view class.
*
* @param string $errorMsg
* @param array $errorMsgArgs
* @param callable $replacingCallable
* @return \MvcCore\Ext\Forms\Field
*/
public function AddValidationError (
$errorMsg = '', array
$errorMsgArgs = [],
callable $replacingCallable = NULL
) {
$errorMsg = $this->translateAndFormatValidationError(
$errorMsg, $errorMsgArgs, $replacingCallable
);
$this->form->AddError($errorMsg, $this->name);
return $this;
}
/**
* Format form error with given error message containing
* possible replacements for array values.
*
* If there is necessary to translate form elements
* (form has configured property `translator` as `callable`)
* than given error message is translated first before replacing.
*
* Before error message processing for replacements,
* there is automatically assigned into first position into `$errorMsgArgs`
* array (translated) field label or field name and than
* error message is processed for replacements.
*
* If there is given some custom `$replacingCallable` param,
* error message is processed for replacements by custom `$replacingCallable`.
*
* If there is not given any custom `$replacingCallable` param,
* error message is processed for replacements by static `Format()`
* method by configured form view class.
*
* @param string $errorMsg
* @param array $errorMsgArgs
* @param callable $replacingCallable
* @return string
*/
protected function translateAndFormatValidationError (
$errorMsg = '', array
$errorMsgArgs = [],
callable $replacingCallable = NULL
) {
$customReplacing = $replacingCallable !== NULL;
$fieldLabelOrName = '';
if ($this->translate) {
$errorMsg = $this->form->Translate($errorMsg);
$fieldLabelOrName = $this->label
? $this->form->Translate($this->label)
: $this->name;
} else {
$fieldLabelOrName = $this->label
? $this->label
: $this->name;
}
array_unshift($errorMsgArgs, $fieldLabelOrName);
$formViewClass = $this->form->GetViewClass();
if ($customReplacing) {
$errorMsg = call_user_func(
$replacingCallable,
$errorMsg, $errorMsgArgs, $formViewClass
);
} else if (strpos($errorMsg, '{0}') !== FALSE || strpos($errorMsg, '{1}') !== FALSE) {
$errorMsg = $formViewClass::Format($errorMsg, $errorMsgArgs);
}
return $errorMsg;
}
/**
* Throw new `\InvalidArgumentException` with given
* error message and append automatically current class name,
* current form id, form class type and current field class type.
* @param string $errorMsg
* @throws \InvalidArgumentException
*/
protected function throwNewInvalidArgumentException ($errorMsg) {
$str = '['.get_class().'] ' . $errorMsg . ' (';
if ($this->form) {
$str .= 'form id: `'.$this->form->GetId() . '`, '
. 'form type: `'.get_class($this->form).'`, ';
}
if (property_exists($this, 'name') && isset($this->name))
$str .= 'field name: `' . $this->name . '`, ';
$str .= 'field type: `' . get_class($this) . '`)';
throw new \InvalidArgumentException($str);
}
}