Yii 2 How add a custom validation check on file upload - php

I am building a CSV uploader and I want to add a custom validation function that will check the header row of the CSV file to ensure the correct columns are in place.
I am trying to put a custom validation rule in the model to do this but failing at the first hurdle.
I am getting
Setting unknown property: yii\validators\FileValidator::0
exception but as far as I can tell from the documentation this should work.
Model
/**
* UploadForm is the model behind the upload form.
*/
class UploadForm extends Model
{
/**
* #var UploadedFile file attribute
*/
public $file;
/**
* #return array the validation rules.
*/
public function rules()
{
return [
[['file'], 'file', 'extensions' => 'csv', 'checkExtensionByMimeType'=>false, 'headerCheck', 'skipOnEmpty' => false]
];
}
public function attributeLabels(){
return [
'file'=>'Select csv'
];
}
function headerCheck($attribute, $params, $validato){
$this->addError($attribute, "error");
}
}
Controller function:
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstance($model, 'file');
$filename = $model->file->baseName . '.' . $model->file->extension;
if ($model->file && $model->validate()) {
$upload = $model->file->saveAs('uploads/'.$filename );
if($upload){
define('CSV_PATH','uploads/');
$csv_file = CSV_PATH . $filename;
$filecsv = file($csv_file);
foreach($filecsv as $data){
$lines = explode(',',$data);
$t=1;
}
}
}
}
return $this->render('csvUpload', ['model' => $model]);
}
View
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model, 'file')->fileInput() ?>
<button>Submit</button>
<?php ActiveForm::end() ?>
Why is headerCheck() not getting picked up as a custom validation function?

Short Answer
Your rules should be written like so:
return [
[['file'], 'file', 'extensions' => 'csv', 'checkExtensionByMimeType'=>false, 'skipOnEmpty' => false],
[["file"], "headerCheck"],
];
Note that your validation rule, "headerCheck", is a separate item in the array.
Long Answer
A rule's structure is like so:
[["attributes_to_validate"],"vaildatorCallback","param1"=>"value","param2"=>"value]
Note the first two items are the attributes and the callback respectively, and then after that you can specify params that should be assigned to the validator, or passed to your validator callback. These params are expected in a form where the key is the name of the property, and the value is the value to assign to the property.
In the example you provided, Yii sees that you want to utilize the "file" validator, so it creates an instance of yii\validators\FileValidator. It then sees that you want the parameter "extensions" set to "csv", so it does:yii\validators\FileValidator::$extensions = "csv"; But then, because you have included your custom validator in this part of the array, it thinks that "headerCheck" is actually a value of a property you want to assign to the validator. Because you have entered this "param" without a key, the key defaults to 0 and so Yii thinks the property you want to assign is called '0'. Thus, Yii attempts this: yii\validators\FileValidator::0 = "headerCheck";
Of course, there is no property '0' on FileValidator, and so that's where the error you're getting is coming from.

Related

Value not updated in databases after validation and save success

Hi everyone i'm having trouble with my software developed with yii2.
I Have a model called Anagrafica and with its primary key id. With this model everything works.
I also have a model called AnagraficaOpzioniCarriera which extend the first one.
I have a view anagrafica/index that show a Kartik grid with the data of people enrolled that you can find in anagrafica. Admin user can update the data of an Anagrafica model by clicking on an the attribute "cognome" that render to anagrafica/update.
this is the command that call the controller AnagraficaController to reach anagrafica/update
'cognome'=>Grid::Labels('cognome',['anagrafica/update'],\app\helpers\Permits::allow('anagrafica','update'),'id','btn','info','10%'),
This is AnagraficaController
public function actionUpdate($id,$error=0,$message='')
{
$id = (int)$id;
$model = Anagrafica::findOne(['id' => $id]);
$model->scenario = 'update';
if ($model->load(Yii::$app->request->post())) {
if($model->validate()){
}
if($model->save(false)){
return $this->redirect(['anagrafica/update','id'=>$model->id]);
}
}
}
return $this->render('update', ['model' => $model, 'extended'=>true]);
}
i removed some portions of code to semplify it, but this is the core.
One time the view anagrafica/update is reached in this page i have an ActiveForm to modify data of the model and i have a render to a grid that show the attributes contained in AnagraficaOpzioniCarriera about the $model that i'm updating.
<?= $this->render('_opzioni_carriera',['parent'=>$model]); ?>
anagrafica/_opzioni_carriera view contain a Kartik grid that shows the column in the model AnagraficaOpzioniCarriera
<?php
use kartik\grid\GridView;
use kartik\select2\Select2;
use kartik\widgets\ActiveForm;
use kartik\editable\Editable;
use kartik\widgets\SwitchInput;
use yii\helpers\ArrayHelper;
use app\helpers\Autoconfigurazione;
use app\models\AnagraficaOpzioniCarriera;
use app\helpers\Grid;
use yii\helpers\Html;
use app\helpers\UserInfo;
/* #var $this yii\web\View */
/* #var $model app\models\AnagraficaOpzioniCarriera*/
$model = new AnagraficaOpzioniCarriera(['scenario'=>'search']);
?>
<div class="">
<?php
echo GridView::widget([
'options'=>[
'id'=>'opzioni_carriera',
],
'dataProvider'=> $model->search($parent->id,Yii::$app->request->queryParams),
'showPageSummary'=>false,
'headerRowOptions'=>['class'=>'kartik-sheet-style'],
'pjax'=>true, // pjax is set to always true for this demo
'pjaxSettings'=>[
'neverTimeout'=>true,
],
'toolbar'=> [
[
'content'=>''
],
],
'panel'=>[
'heading'=>false,
'footer'=>false,
'after'=>false,
],
'columns' => Grid::gridColumns([
'model'=>$model,
'checkbox'=>false,
'remove'=>Grid::gridRemove($model),
'extraOptions' =>[
'cashback' =>Grid::YNColumn('cashback',['anagrafica-opzioni-carriera/update', 'id' => $parent->id],'left',true,'5%'),
'compensa'=>Grid::YNColumn('compensa',['anagrafica-opzioni-carriera/update', 'id' => $parent->id],'left',true,'5%'),
'associazione'=>Grid::YNColumn('associazione',['anagrafica-opzioni-carriera/update', 'id' => $parent->id],'left',true,'5%'),
'formazione'=>Grid::YNColumn('formazione',['anagrafica-opzioni-carriera/update', 'id' => $parent->id],'left',true,'5%'),
],
]);
?>
</div>
cashback, compensa etc.. are the attributes in the model AnagraficaOpzioniCarriera.
Here when i try to update this attributes everything looks fine, the function model->validate() and model->load returns true value, but at the end of the process doesn't works.
Honestly i don't know what i have to return from the function of the controller.
public function actionUpdate($id)
{
$model = AnagraficaOpzioniCarriera::findOne(['id_anagrafica' => $id]);
if (!$model) {
// Se l'anagrafica opzioni carriera non esiste, genera un'eccezione 404
throw new \yii\web\NotFoundHttpException(Yii::t('app', 'The requested page does not exist.'));
}
$model->scenario = 'update';
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
if(Yii::$app->request->post('cashback') != null) $model->cashback = Yii::$app->request->post('cashback');
if(Yii::$app->request->post('compensa') != null) $model->cashback = Yii::$app->request->post('compensa');
if(Yii::$app->request->post('associazione') != null) $model->cashback = Yii::$app->request->post('associazione');
if(Yii::$app->request->post('formazione') != null) $model->cashback = Yii::$app->request->post('formazione');
if ($model->save()) {
return Json::encode(["success" => true, 'message' => 'Dati aggiornati']);
}
}
// Mostra il form di modifica
return $this->render('_opzioni_carriera', [
'parent' => $model,
]);
}
anyone can help me? i hope i explained my problem in a good form, but my english is not the best, i know. Anyway thanks in aadvance to everyone who want to try to help me, if you need anything other you can easily ask.
I tried every everything, also a logger but nothing worked
Like someone suggest these are the rules of the model AnagraficaOpzioni, but like i said prevously model->validate() works, for this reason i think the problem is not over there
public function rules()
{
return [
[['id_anagrafica'], 'required'],
[['id_anagrafica'], 'integer'],
[['cashback', 'compensa', 'associazione', 'formazione'], 'required', 'on'=>['update']],
[['cashback', 'compensa', 'associazione', 'formazione'], 'integer'],
[['id_anagrafica', 'cashback', 'compensa', 'associazione', 'formazione',], 'safe', 'on'=>['search']],
];
}

How to get all possible error messages of a Laravel Controller method

So I have a Laravel Application, which has many Controllers to handle various aspects of the applications.
Now each controller has various methods. Most of the methods have validations rules defined such as:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray);
if ($validator->fails()){
return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);
}
Now the following line:
return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);
actually returns whatever is wrong with the validation rules.
My question is: Is there any way to get all possible error messages programmatically?
Of course, one way to do it is going around the rule by rule and make a list manually but there are hundreds of the methods scattered over various controllers.
So, if anyone could point me in the direction of taking all the error messages in some easier way, would be much appreciated.
Thank you in advance!
UPDATE
So to clear further I need a list of all possible errors, like for above code the list will be like:
['id is required', 'id must be an integer', 'status is required', 'status must be an string']
UPDATE 2
Please keep in mind that there are hundreds of methods and also I do not want to change the final response of the method but to have some sort of external script which can help me getting the error messages without interfering with the controllers much.
In order to do that you have to extend Validator class and write a method that will iterate all rules and explicitly add error messages as if they failed.
First, create a new file app\Http\Custom\Validator.php:
<?php
namespace App\Http\Custom;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Validation\Validator as BaseValidator;
class Validator extends BaseValidator {
/** #var MessageBag */
protected $errorMessages;
/** #var array */
protected $hasExplicitFileErrorMessage;
protected $explicitFileRules = [
'File', 'Image', 'Mimes', 'Mimetypes', 'Dimensions',
];
function availableErrors()
{
$this->errorMessages = new MessageBag();
$this->hasExplicitFileErrorMessage = [];
foreach($this->rules as $attribute => $rules) {
$attribute = str_replace('\.', '->', $attribute);
foreach($rules as $rule) {
[$rule, $parameters] = ValidationRuleParser::parse($rule);
if($rule == '') {
continue;
}
if(($keys = $this->getExplicitKeys($attribute)) &&
$this->dependsOnOtherFields($rule)) {
$parameters = $this->replaceAsterisksInParameters($parameters, $keys);
}
// explicitly add "failed to upload" error
if($this->hasRule($attribute, $this->explicitFileRules) && !in_array($attribute, $this->hasExplicitFileErrorMessage)) {
$this->addFailureMessage($attribute, 'uploaded', []);
$this->hasExplicitFileErrorMessage[] = $attribute;
}
if($rule instanceof RuleContract) {
$messages = $rule->message() ? (array)$rule->message() : [get_class($rule)];
foreach($messages as $message) {
$this->addFailureMessage($attribute, get_class($rule), [], $message);
}
} else {
$this->addFailureMessage($attribute, $rule, $parameters);
}
}
}
return $this->errorMessages->all();
}
function addFailureMessage($attribute, $rule, $parameters = [], $rawMessage = null)
{
$this->errorMessages->add($attribute, $this->makeReplacements(
$rawMessage ?? $this->getMessage($attribute, $rule), $attribute, $rule, $parameters
));
}
// we have to override this method since file-type errors depends on data value rather than rule type
protected function getAttributeType($attribute)
{
if($this->hasRule($attribute, $this->explicitFileRules)) {
return 'file';
}
return parent::getAttributeType($attribute);
}
}
Next, let's register this class in Validation factory:
<?php
namespace App\Providers;
use App\Http\Custom\Validator; // <-- our custom validator
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
public function boot()
{
app('validator')->resolver(function ($translator, $data, $rules, $messages) {
return new Validator($translator, $data, $rules, $messages);
});
}
}
And... that's all. Let's test it:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class HomeController extends Controller {
function index(Request $request)
{
$rules = [
'id' => 'required|int|between:2,10',
'status' => 'required_with:nonexisting|string|email',
'avatar' => 'required|file|mimes:png|max:1000',
'company' => 'required_without:id|unique:companies,id'
];
$validator = Validator::make([], $rules);
dump($validator->availableErrors());
}
}
array:13 [▼
0 => "The id field is required."
1 => "The id must be an integer."
2 => "The id must be between 2 and 10."
3 => "The status field is required when nonexisting is present."
4 => "The status must be a string."
5 => "The status must be a valid email address."
6 => "The avatar failed to upload."
7 => "The avatar field is required."
8 => "The avatar must be a file."
9 => "The avatar must be a file of type: png."
10 => "The avatar may not be greater than 1000 kilobytes."
11 => "The company field is required when id is not present."
12 => "The company has already been taken."
]
It isn't pretty but here's my shot:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(), $validationArray);
if ($validator->fails()) {
$messages = [];
$invalid_fields = array_keys($validator->messages()->toArray());
$rules = $v->getRules();
foreach($invalid_fields as $invalid_field) {
foreach($rules[$invalid_field] as $rule) {
if(str_contains($rule, ':') {
// complex rules that have parameters (min, between, size, format)
// are more difficult to work with. I haven't figured out how to do them yet
// but you should get the idea.
continue;
} else {
$messages[] = str_replace(':attribute', $invalid_field, $validator->getTranslator()->get("validation.$rule"));
}
}
}
return Response::json(['response' => implode(', ', $messages)], 422);
}
Number 1: Like I mentioned in my comment under the question, what you're trying to achieve may be done in simpler way.
Number 2: Since you do not want to change your already written code where you got ->messages() then you could do the following. I will list the steps and provide an example code.
We need to override Laravel's validator, (Validation) Factory, and ValidationService provider classes.
In App\Services folder you can create two classes Validator and ValidationFactory
in App\Providers create a class ValidationServiceProvider
Go into config/app.php file and under providers replace Illuminate\Validation\ValidationServiceProvider::class with App\Providers\ValidationServiceProvider::class
Validator class looks like so:
namespace App\Services;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Contracts\Translation\Translator;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Illuminate\Contracts\Validation\Rule as RuleContract;
class Validator extends \Illuminate\Validation\Validator
{
/**
* #var MessageBag $all_messages
*/
protected $all_messages;
public function __construct(Translator $translator, array $data, array $rules, array $messages = [], array $customAttributes = [])
{
parent::__construct($translator, $data, $rules, $messages, $customAttributes);
$this->all_messages = new MessageBag;
$this->getAllFormattedMessages();
}
public function makeAllRulesMessages($attribute, $rule, $parameters)
{
$this->all_messages->add($attribute, $this->makeReplacements(
$this->getMessage($attribute, $rule), $attribute, $rule, $parameters
));
}
public function messages(bool $validated_rules_messages = false)
{
return $validated_rules_messages
? $this->validatedMessages()
: $this->all_messages;
}
/**
* This is here in case the true validated messages are needed
*
* #return MessageBag
*/
public function validatedMessages()
{
return parent::messages();
}
public function getAllFormattedMessages()
{
// We'll spin through each rule and add all messages to it.
foreach ($this->rules as $attribute => $rules) {
$attribute = str_replace('\.', '->', $attribute);
foreach ($rules as $rule) {
// First we will get the correct keys for the given attribute in case the field is nested in
// an array. Then we determine if the given rule accepts other field names as parameters.
// If so, we will replace any asterisks found in the parameters with the correct keys.
[$rule, $parameters] = ValidationRuleParser::parse($rule);
if (($keys = $this->getExplicitKeys($attribute)) &&
$this->dependsOnOtherFields($rule)) {
$parameters = $this->replaceAsterisksInParameters($parameters, $keys);
}
$value = $this->getValue($attribute);
if ($value instanceof UploadedFile && $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
) {
$this->makeAllRulesMessages($attribute, 'uploaded', []);
} elseif ($rule instanceof RuleContract) {
$this->makeCustomRuleMessage($attribute, $rule);
} else {
$this->makeAllRulesMessages($attribute, $rule, $parameters);
}
}
}
}
/**
* #param $attribute
* #param \Illuminate\Contracts\Validation\Rule $rule $rule
*/
public function makeCustomRuleMessage($attribute, $rule)
{
$this->failedRules[$attribute][get_class($rule)] = [];
$messages = (array)$rule->message();
foreach ($messages as $message) {
$this->all_messages->add($attribute, $this->makeReplacements(
$message, $attribute, get_class($rule), []
));
}
}
}
This class does one thing in summary, get all the messages of the passed rules into $all_messages property of the class. It extends and allows the base validation class run, and simply overrides messages() method to make all the collected rules available for use.
ValidationFactory overrides Illuminate\Validation\Factory and it looks like so:
namespace App\Services;
use Illuminate\Validation\Factory;
class ValidationFactory extends Factory
{
/**
* Resolve a new Validator instance.
*
* #param array $data
* #param array $rules
* #param array $messages
* #param array $customAttributes
* #return \Illuminate\Validation\Validator
*/
protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
{
if (is_null($this->resolver)) {
return new \App\Services\Validator($this->translator, $data, $rules, $messages, $customAttributes);
}
return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
}
}
This class does only one thing, overrides resolve() method in this class by making use of the instance of our custom \App\Services\Validator class instead.
ValidationServiceProvider extends Illuminate\Validation\ValidationServiceProvider and overrides registerValidationFactory() method and it looks like so:
namespace App\Providers;
use App\Services\ValidationFactory;
use Illuminate\Validation\ValidationServiceProvider as BaseValidationServiceProvider;
class ValidationServiceProvider extends BaseValidationServiceProvider
{
protected function registerValidationFactory()
{
$this->app->singleton('validator', function ($app) {
$validator = new ValidationFactory($app['translator'], $app);
// The validation presence verifier is responsible for determining the existence of
// values in a given data collection which is typically a relational database or
// other persistent data stores. It is used to check for "uniqueness" as well.
if (isset($app['db'], $app['validation.presence'])) {
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
}
What the above class does is also to instruct the provide to make use of our App\Services\ValidationFactory whenever the app requires one.
And we are done. All validation messages will be shown even if one of our validation rules failed.
Caveats
In order to achieve this, we needed to make a lot of changes and overriding. Except really critical this may signal that something about the app's design looks wrong.
Laravel validation implementation may change in future release and therefore may become a problem maintaining these changes.
I cannot tell if there are other side effects that might happen for overriding Laravel's default validation implementation or if all the rules return the right messages.
Normally you only want to return failed validation messages to user rather than all the possible failures.
I think that functions failed() (get the failed validation rules) or errors() (get the message container for the validator) may help you. If it does not - go to https://laravel.com/api/5.8/Illuminate/Validation/Validator.html and I hope that you find needed function.
I think you are looking for a way to have custom error messages. if this is the case then the answer is like this:
$messages = [
'id.required' => 'id is required',
'id.integer' => 'id must be an integer',
'status.required' => 'status is required',
'status.string'=> 'status must be an string'
];
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray, $messages);
more info you can find here.
I hope this is what you are looking for and my answer is helping you:)
Based on the Laravel Form Validation Procedure you can write the statement by following:
$validationArray = [
'id'=>'required|integer',
'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray);
if ($validator->fails()){
return Response::json(['response'=> validator->errors())],422);
}
Where errors() method return all the errors as associative array where the message will be associate with the field name accordingly and that's how you can get the errors.

CodeIgniter 4 - Validation Custom Rule Function Quandry

In my CI4 learning, I have started by trying to simulate user sign in functionality. I have a Controller, two Views (not shown here, but really simply pages- one a pretty much just single form, and the other one a “blank” success HTML page), a set of custom rules in the Validation.php file, and a CustomRule.php file with the first of the methods that will implement all my custom rules (which, ultimately, I’d like to have all set in the Validation.php file). For lack of a better idea, I’ve stuck the CustomRules.php file in the app\Config\ folder.
Here is my problem:
For the life of me, I can’t figure out how to get the Validation service to pass additional parameters (from the form) to my custom rules function called ‘user_validated’. The CI4 documentation describes what the custom function needs to cater for when accepting additional parameters, but not how to trigger the Validation service to pass these additional parameters to one’s custom function… so although ‘user_validated’ is called, only ‘user_email_offered’ is ever passed as in as a string- nothing else goes in, from what I can tell. How do I get around this?
I have tried inserting < $validation->setRuleGroup('user_signin'); > before the call to validate, but found that I could move the setting of the rule group into the call to validate, using: $validationResult = $this->validate('user_signin'), which seemed to do the same, and which doesn't seem to work without the rule-group as a parameter (?). This still doesn't seem to be what triggers the additional data to be passed to the custom rule's method.
Extracts from my hack are appended below.
I’d be very grateful one of you knowledgeable folk could please point me in the right direction.
In app\Controllers\SignupTest.php:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class SignupTest extends BaseController
{
public function index() { // redirection from the default to signup(), signin(), ...
return $this->signup();
}
public function signup() {
helper(['form']);
$validation = \Config\Services::validation();
if ($this->request->getPost()) { // still TBD: any different to using $this->request->getGetPost() ?
$validationResult = $this->validate('user_signin'); // set the rules to use: 'user_signin', 'user_signup'
if (!$validationResult) {
$validationErrors = $validation->getErrors();
return view('SignupTestView', $validationErrors); // redisplay simple html form view with list of validation errors
} else {
return view('SignupTestViewSuccess'); // display view to show success
}
} else {
return view('SignupTestView'); // initial display, in the event of there being no POST data
}
}
}
In \app\Config\CustomRules.php:
<?php
namespace Config;
use App\Models\UserModel;
//--------------------------------------------------------------------
// Custom Rule Functions
//--------------------------------------------------------------------
class CustomRules
{
public function user_validated(string $str, string $fields = NULL, array $data = NULL, string &$error = NULL) : bool{
$user_email_offered = $str;
$user_password_offered = ''; // to be extracted using $fields = explode(',', $fields), but $fields is never provided in the call to this user_validated method
if (($user_email_offered !== NULL) && ($user_password_offered !== NULL)) {
$usermodel = new UserModel(); // intended to create a UserEntity to permit connectivity to the database
$user_found = $usermodel->find($user_email_offered); // we're going to assume that user_email is unique (which is a rule configured in the database table)
if ($user_found === NULL) { // check if user exists before doing the more involved checks in the else-if section below, which may throw exceptions if there's nothing to compare (?)
...
}
}
In \app\Config\Validation.php:
?php
namespace Config;
class Validation
{
//--------------------------------------------------------------------
// Setup
//--------------------------------------------------------------------
/**
* Stores the classes that contain the
* rules that are available.
*
* #var array
*/
public $ruleSets = [
\CodeIgniter\Validation\Rules::class,
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\FileRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
\Config\CustomRules::class,
];
/**
* Specifies the views that are used to display the
* errors.
*
* #var array
*/
public $templates = [
'list' => 'CodeIgniter\Validation\Views\list',
'single' => 'CodeIgniter\Validation\Views\single',
];
//--------------------------------------------------------------------
// Custom Rules
//--------------------------------------------------------------------
/* configurable limits for validation rules array below*/
const user_email_min_lenth = 9;
const user_email_max_lenth = 50;
const user_password_min_lenth = 6;
const user_password_max_lenth = 25;
public $user_signin = [
'user_email' => [
'label' => 'e-mail address',
'rules' => 'trim|required|valid_email|user_validated', // user_validated is custom rule, that will have a custom error message
'errors' => [
'required' => 'You must provide an {field}',
'valid_email' => 'Please enter a valid {field}',
]
],
'user_password' => [
'label' => 'password',
'rules' => 'trim|required',
'errors' => [
'required' => 'Enter a {field} to sign in',
'user_password_check' => 'No such user/{field} combination found',
]
Calling custom rule with parameters should be exactly the same as calling CI4's regular rules. Let's get for example "required_without". You use it like in this example:
$validation->setRule('username', 'Username', 'required_without[id,email]');
And the function is declared as so:
public function required_without($str = null, string $fields, array $data): bool
{
$fields = explode(',', $fields);
//...
}
where $str - this is your main field, $fields - string, packing a comma-separated array.
As for Grouping rules, you do not need to group rules to be able to use custom rules with parameters.
If you have only 2 fields to test against you can go a bit cheaper, which will not be perfect but still works:
Function:
public function myrule(string $mainfield, string $fieldtotestwith): bool
{
//doing stuff
}
Validating rule:
$validation->setRule('somemainfield', 'Something', 'myrule[somesecondfield]');

how to update form that contains filefield without re-upload the file again in Yii?

I want to update a record in my model, and it contains a file field, here is the form code:
<div class="row">
<?php echo $form->labelEx($model,'img'); ?>
<?php echo CHtml::activeFileField($model,'img',array('width'=>25,'lentgh'=>25)); ?>
<?php echo $form->error($model,'img'); ?>
</div>
and here is the update action in the controller:
$model=$this->loadModel($id);
$img = $model->img;
if(isset($_POST['Press']))
{
$model->attributes=$_POST['Press'];
$model->img = $img;
if(isset($_POST['Press']['img'])){
if(!empty ($_POST['Press']['img']))
$model->img = CUploadedFile::getInstance($model, 'img');
So if the user didn't upload an image, the value of img attribute should not be updated and the model should be validated, but I got validation error every time I click on save and the img file filed is empty, so how I can fix this issue ?
Form validations are handeled by the model.
You can set an imagefield of filefield to be allowed to be empty like this in your model.php:
array('image', 'file', 'types'=>'jpg,png,gif',
'maxSize'=>1024 * 1024 * 5,
'allowEmpty' => true),
EDIT:
You can check if the file is empty before overwriting the current value of the object
$imageUploadFile = CUploadedFile::getInstance($model, 'image');
if($imageUploadFile !== null){ // only do if file is really uploaded
$imageFileName = mktime().$imageUploadFile->name;
$model->image = $imageFileName;
}
You can use csenario as:
array('image', 'file', 'types'=>'jpg,png,gif',
'maxSize'=>1024 * 1024 * 5,
'allowEmpty' => false, 'on'=>'insert'),
array('image', 'file', 'types'=>'jpg,png,gif',
'maxSize'=>1024 * 1024 * 5,
'allowEmpty' => true, 'on'=>'update'),
after that it will allow empty field on update
Faced the same issue and here is my solution.
But first I will describe how I work with models.
Basic POST processing
if (Yii::app()->request->getIsPostRequest()) {
$basicForm->attributes = Yii::app()->request->getParam(get_class($basicForm));
if ($basicForm->validate()) {
$logo = CUploadedFile::getInstance($basicForm, "logo");
if (!is_null($logo)) {
try {
// uploads company logo to S3
} catch (\Exception $e) {
// display any kind of error to the user or log the exception
}
}
if ($basicForm->save(false))
$this->refresh();
}
}
and my logo field has rule ["logo", "file", "mimeTypes" => ["image/png", "image/jpeg"], "allowEmpty" => true].
This rule gives me freedom to upload or not to upload the file, BUT if I want to change another form field not changing the file it will empty the model's logo field and my database too.
Problem
File gets empty if form update trying to update another form field, not file
Why it happening?
This happening because file validator expects CUploadedFile type of object when you have string in the model's logo field. String from database where you storing path to logo. And string is not CUploadedFile. And after if ($basicForm->validate()) model resets the logo field to null.
Solution
Own validation rule which will upload/reupload the file if logo is of hype `` and do nothing if logo of basic string type. Here I will put a basic "in-model" validator, it is up to you to move it into separate class, etc.
public function checkFile($attribute, $params)
{
$mimeTypes = isset($params["mimeTypes"]) ?$params["mimeTypes"] :[];
$allowEmpty = isset($params["allowEmpty"]) ?$params["allowEmpty"] :false;
/** #var CUploadedFile $value */
$value = $this->{$attribute};
if (!$allowEmpty && empty($value))
$this->addError($attribute, "{$attribute} can not be empty");
if (!empty($value)) {
if (is_object($value) && CUploadedFile::class === get_class($value) && 0 < sizeof($mimeTypes)) {
if (!is_array($value->type, $mimeTypes))
$this->addError($attribute, "{$attribute} file is of wrong type");
} elseif (!is_string($value)) {
// we can die silently cause this error won't actually ever get to the user in normal use cases
$this->addError($attribute, "{$attribute} must be of type either CUploadedFile or PHP string");
}
}
}
I called it checkFile and then the logo rule becomes ["logo", "checkFile", "mimeTypes" => ["image/png", "image/jpeg"], "allowEmpty" => true],
That is it. Enjoy.
Note: I'm putting this here as an example. Code may not be completely correct, or used as is, all the stuff like that... :)
I'had same problem. In my user model there wos a field containing path to image and i wanted to update model and fileField cleared my images.
So i figured workaround - store field containing path_to_file at start of action "update" in $tmp_variable and if there is no new upload - just set it before save():
public function actionUpdate($id)
{
$request = Yii::app()->request;
$model = $this->loadModel($id, 'YourModel');
$tmpPathToImage = $model->your_path_to_file_in_model;
$yourModelPost = $request->getPost('YourModel');
if (!empty($yourModelPost)) {
$model->setAttributes($yourModelPost);
$fileSource = Yii::getPathOfAlias('webroot.uploads.yourModel');
$imgTmp = CUploadedFile::getInstance($model, 'your_path_to_file_in_model');
if ($imgTmp !== null) {
$imgTmp->saveAs($fileSource.'/'.$imgTmp->name);
$model->your_path_to_file_in_model = '/'.UPLOADS_YOUR_MODEL_FILE_PATH_RELATIVE.'/'.$imgTmp->name;
}
else {
$model->your_path_to_file_in_model = $tmpPathToImage;
}
if ($model->save()) {
$this->redirect(['/admin/yourModel/index']);
}
}
$this->render('update', [
'model' => $model,
]);
}
Hope it helps someone... :)

model->attributes in Yii2 always has NULL value

I have one temporary model as viewModel. In my CRUD actions (for example actionCreate) I want to get this viewModel data and assign that to a ActiveRecord model. I used below code but my model object atrribute always show NULL value for attributes:
$model = new _Users();
if ($model->load(Yii::$app->request->post())) {
Yii::info($model->attributes,'test'); // NULL
$attributesValue =[
'title' => $_POST['_Users']['title'],
'type' => $_POST['_Users']['type'],
];
$model->attributes = $attributesValue;
Yii::info($model->attributes,'test'); // NULL
$dbModel = new Users();
$dbModel->title = $model->title;
$dbModel->type = $model->type . ' CYC'; // CYC is static type code
Yii::info($dbModel->attributes,'test'); // NULL
if ($dbModel->save()) {
return $this->redirect(['view', 'id' => $dbModel->id]); // Page redirect to blank page
}
}
else {
return $this->render('create', [
'model' => $model,
]);
}
I think $model->load(Yii::$app->request->post()) not working and object attribute being NULL. Is it Yii2 bug or my code is incorrect??
If there is no rule for your attribute the $model->load() will ignore those not in the rules of the model.
Add your attributes to the rules function
public function rules()
{
return [
...
[['attribute_name'], 'type'],
...
];
}
To fetch data for an individually attributes(db-fields) in yii2.0 then you should just do as:
echo $yourModel->getAttribute('email');
ActiveRecord $attributes is a private property
Use $model->getAttribute(string)
You can use following codes:
$model = new _Users();
$model->attributes=Yii::$app->request->post('_Users');
$model->title= $model->title
$model->type = $model->type . ' CYC'; // CYC is static type code
#$model->sampleAttribute='Hello World';
Declare attribute as private then
echo $yourModel->attribute
work as expected
You must remove all public properties (title, type, etc.) in your _User model and $model->attributes = $post will work correctly.
I have also encountered the same problem, i Add my attributes to the rules function,but also error. And i found the reason for this problem. It is beause that the submit form's name in corresponding view file is not the same as the model's name which you use in controller
[controller file]:
$model=new SearchForm();
[view file]:
<input name="SearchForm[attribus]" ...
or
[view file]:
<?= $form->field($model,'atrribus')->textInput()?>

Categories