Yii2 Validate multiple models - model-view-controller

I have two models in Yii2 (masterTransaction and splitTransaction), where each masterTransactions can have multiple splitTransactions. Each splitTransaction has an attribute 'amount'. My problem is I need to validate if the sum over all 'amount' attributes is 0.
My first solution was to make another model called Transaction, in which I had an attribute where I saved an instance of the masterTransaction model and another attribute with an array of splitTransaction instances. I did the validation with a custom inline validatior, which work perfectly.
Transaction model
class Transaction extends Model
{
public $masterTransaction;
public $splitTransactions;
public function init()
{
$this->masterTransaction = new MasterTransaction();
$this->splitTransactions[] = new SplitTransaction();
}
public function rules()
{
return [
['splitTransactions', 'validateSplitTransactions'],
];
}
public function validateSplitTransactions($attribute, $params)
{
$sum = 0;
foreach ($this->$attribute as $transaction) {
$sum = bcadd($sum, $transaction->amount, 3);
}
if ($sum != 0) {
$this->addError($attribute, 'The sum of the entries has to be 0');
}
}
public function save()
{
$this->masterTransaction->save();
foreach ($this->splitTransactions as $splitTransaction) {
$splitTransaction->master_transaction_id = $this->masterTransaction->id;
$splitTransaction->save();
}
}
}
Controller function to create the model
public function actionCreate()
{
$transaction = new Transaction();
$count = count(Yii::$app->request->post('SplitTransaction', []));
for ($i = 1; $i < $count; $i++) {
$transaction->splitTransactions[] = new SplitTransaction();
}
if ($transaction->masterTransaction->load(Yii::$app->request->post()) && Model::loadMultiple($transaction->splitTransactions, Yii::$app->request->post())) {
$transaction->masterTransaction->user_id = Yii::$app->user->id;
foreach ($transaction->splitTransactions as $splitTransaction) {
$splitTransaction->user_id = Yii::$app->user->id;
}
if ($transaction->validate()) {
$transaction->save();
}
}
return $this->render('create', [
'transaction' => $transaction,
]);
}
But when I tried building a form to input the data, I ran into a problem with the Ajax validation. The validation would work, but Yii didn't know where to put the error message, so it just deleted it.
I suspect that this is just not the preferred way in Yii2 model my data, but I don't really have another idea. Maybe someone has some ideas for me.

Option 1.
It depends on your view file codes. Does your form contains "splitTransactions" variable? If not, you can put it like this
<?= $form->field($model, 'splitTransactions')->hiddenInput(['maxlength' => true])->label(false); ?>
The variable will be hidden, but still show errors. In some case validation will not be fired because of empty value of "splitTransactions" variable.
"splitTransactions" should contain some value to fire validation. You can put some value to if before pasting the form like this
$model->splitTransactions=1;
Option 2.
You can add error to other variable (which form contains) like this
public function validateSplitTransactions($attribute, $params)
{
$sum = 0;
foreach ($this->$attribute as $transaction) {
$sum = bcadd($sum, $transaction->amount, 3);
}
if ($sum != 0) {
$this->addError('transaction_number', 'The sum of the entries has to be 0');
}
}
Look, form should contain "transaction_number" variable. Error will be added to "transaction_number" input.
Option 3. In my experience.
It is better to separate ajax validation from form action url a.g. create another controller action for ajax validation and use it.
Example
Create model FeedbackForm
class FeedbackForm extends Model
{
public $name;
public $email;
public $text;
/**
* #inheritdoc
*/
public function rules()
{
return [
[['name', 'email', 'text'], 'required'],
[['name', 'email'], 'string', 'max' => 128],
[['email'], 'email'],
[['text'], 'string', 'max' => 512],
];
}
public function attributeLabels()
{
return [
'name' => \Yii::t('front', 'Name'),
'email' => \Yii::t('front', 'Email'),
'text' => \Yii::t('front', 'Message text'),
];
}
}
put actions to SiteSontroller
public function actionFeedback()
{
$model= new \frontend\models\FeedbackForm;
$model->load(Yii::$app->request->post());
if($model->validate()) {
$newFeed=new \frontend\models\Feedback;
$newFeed->create_time=new \yii\db\Expression('NOW()');
$newFeed->name=$model->name;
$newFeed->email=$model->email;
$newFeed->is_new=1;
$newFeed->text=$model->text;
if($newFeed->save()) {
\Yii::$app->session->setFlash('success', \Yii::t('front', 'Your message has accepted'));
} else {
\Yii::$app->session->setFlash('error', \Yii::t('front', 'Error on save'));
}
} else {
\Yii::$app->session->setFlash('error', \Yii::t('front', 'Data error'));
}
return $this->redirect(['/site/index']);
}
public function actionFeedbackvalidate()
{
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model= new \frontend\models\FeedbackForm;
$model->load(Yii::$app->request->post());
return ActiveForm::validate($model);
}
And create form inside view
<?php $model=new \frontend\models\FeedbackForm; ?>
<?php $form = ActiveForm::begin([
'enableClientValidation' => true,
'enableAjaxValidation' => true,
'validationUrl'=>['/site/feedbackvalidate'],
'validateOnSubmit' => true,
'id' => 'form-feedback',
'action'=>['/site/feedback'],
'options'=>['class'=>'some class', 'autocomplete'=>'off']
]); ?>
<?= $form->field($model, 'name')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('name'), 'autocomplete'=>'off'])->label(false); ?>
<?= $form->field($model, 'email')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('email'), 'autocomplete'=>'off'])->label(false); ?>
<?= $form->field($model, 'text')->textarea(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('text'), 'autocomplete'=>'off'])->label(false); ?>
<div class="form-group">
<input type="submit" class="btn btn-default" value="<?php echo Yii::t('front', 'Send') ?>">
</div>
<?php ActiveForm::end(); ?>
That is it

Related

Laravel livewire wire:model with array

From blade component I can give valule product_id
<input wire:model="qty.{{$row->product_id}}" value="{{$row->qty}}" max="{{$row->stock}}">
but how I can display qty from database and when increase qty then wire model will work and updated, what I can should do???
public $qty;
public function render()
{
$this->userId=Auth::id();
if ($this->qty!=null){
foreach($this->qty as $key => $qty)
{
$cart=Cart::where('user_id',$this->userId)->where('product_id',$key)->first();
if ($cart){
$cart->update([
'qty' => $qty,
]);
$this->emit('refreshCart');
}
}
}
}
in your blade
<input wire:model="inputs.{{ $loop->index }}.qty" value="{{$row->qty}}" max="{{$row->stock}}">
<a wire:click.prevent="increase({{$loop->index}})"></a>
<a wire:click.prevent="reduce({{$loop->index}})"></a>
in your livewire component
public $inputs = [];
public function mount()
{
foreach (Cart::where('user_id',$this->userId)->get() as $item) {
array_push($this->inputs, [
"id" => $item->id,
"qty" => $item->quantity,
]);
}
}
public function reduce($index)
{
$product_id = $this->inputs[$index]['id'];
$this->inputs[$index]['qty'] -= 1;
$this->updateCart($product_id, $this->inputs[$index]['qty']);
}
public function increase($index)
{
$product_id = $this->inputs[$index]['id'];
$this->inputs[$index]['qty'] += 1;
$this->updateCart($product_id, $this->inputs[$index]['qty']);
}
public function updateCart($product_id, $productQty)
{
//update yourcart here with productID and qty
}
This code works for me.

Validation message not showing below the field in cakephp 3 latest version

I am trying to validate a multiple select field that have jquery chosen applied.
Validation is working perfectly but only problem is that validation message is not showing below the input field.
Here is my files.
profile_edit.ctp
<?php echo $this->Form->create($user,['action' => '', 'role'=>"form",'novalidate'=>true,'method'=>'post','id'=>'ProfileForm','templates'=>['label'=>false,'inputContainer'=>'{{content}}']]); ?>
<?php echo $this->Form->control('user_grades[].grade_id',['multiple','hiddenField'=>false, 'escape'=>false, 'type'=>'select', 'id'=>'sp_grade', 'options'=>App\Model\Table\GradesTable::getGrades('list'),'class'=>'form-control chosen-select']); ?>
<button type="submit" class="btn footer_btns float-left">Save</button>
<?php echo $this->Form->end(); ?>
MyAccountController.php
<?php
public function profileEdit(){
$user = $this->Users->get($this->Auth->user('id'), ['contain'=>['UserGrades']]);
if($this->request->is(['put','post'])){
$data = $this->request->getData();
if(isset($data['user_grades']) && !empty($data['user_grades'])) {
$this->UserGrades->deleteAll(['user_id' => $this->Auth->user('id')]);
}
if(null == $this->request->getData('user_grades')){
$this->request = $this->request->withData('user_grades.0.grade_id','');
}
$user = $this->Users->patchEntity($user, $this->request->getData(), [
'validate' => 'editProfileSection',
'associated' => [
'UserGrades' => ['validate'=> 'editProfileSection']
]
]);
if(empty($user->getErrors())){
if ($this->Users->save($user)) {
$this->Flash->success(__('Succesfully updated <strong>'.$user->full_name .'</strong> Information||Success'));
return $this->redirect(['action' => '']);
}
}
$this->Flash->error(__('Please check your inputs and try again.||Action Failed!'));
}
$this->set(compact('user'));
}
UserGradesTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\TableRegistry;
use Cake\Event\Event;
use Cake\ORM\RulesChecker;
use Cake\Validation\Validator;
class UserGradesTable extends Table {
public function initialize(array $config) {
$this->addBehavior('Timestamp');
$this->addBehavior('Trim');
}
public function validationEditProfileSection(Validator $validator) {
$validator
->notEmpty('grade_id',"Please select at least one grade.");
return $validator;
}
}
I have tried to get error message and got following:
Array
(
[user_grades] => Array
(
[0] => Array
(
[grade_id] => Array
(
[_empty] => Please select at least one grade.
)
)
)
)
But this error is not showing below the input field. Any help will be appreciated.
You are not using the correct naming scheme for the form control, you cannot use [], if you want the form helper magic to work, then you must supply the actual index, ie:
user_grades.0.grade_id
See also Cookbook > Views > Helpers > Form > Creating Inputs for Associated Data

Yii2 checkboxlist validation without model

I have a list of checkboxes, which aren't associated with the model itself like the rest of the form:
echo Html::checkboxList('options', $selected_options, $options, ['class' => 'checkbox']);
In the model I have a following rules:
public function rules() {
return [
....*/
[['options'], 'integer'],
['options', 'optValidation', 'on' => 'update'],
];
}
And the following validator:
public function optValidation($attribute, $params) {
foreach ($attribute as $attr){
if ($attr == 1) {
$return = true;
}
else {
$return = false;
}
}
if (!$return) {
$this->addError($attribute, 'At least one checkbox has to be selected!');
}
}
Unfortunately nothing happens when submitting the form as if there are no rules for validation of the field 'options'. Where's the catch?
You need to add options as a public attribute inside your model class. Then you need to set it as safe inside your rules:
public function rules() {
...
[['options'], 'integer'],
[['options'], 'optValidation', 'on' => 'update'],
[['options'], 'safe'],
...
Finally, you will have to generate the input just like other inputs related to your model, even if it doesn't exist in your DB table, you need it inside your model:
<?php echo $form->field($model, 'options')->checkboxList('options', $selected_options, $options, ['class' => 'checkbox']); ?>

Phalcon validation scenario

I used to use Yii framework. I would like to make project using Phalcon. I could not find validation scenario on Phalcon. What is the best way to correctly implement it on Phalcon?
Thanks in advance.
Any data validation:
<?php
use Phalcon\Validation\Validator\PresenceOf,
Phalcon\Validation\Validator\Email;
$validation = new Phalcon\Validation();
$validation->add('name', new PresenceOf(array(
'message' => 'The name is required'
)));
$validation->add('email', new PresenceOf(array(
'message' => 'The e-mail is required'
)));
$validation->add('email', new Email(array(
'message' => 'The e-mail is not valid'
)));
$messages = $validation->validate($_POST);
if (count($messages)) {
foreach ($messages as $message) {
echo $message, '<br>';
}
}
http://docs.phalconphp.com/en/1.2.6/reference/validation.html
If you are working with models:
<?php
use Phalcon\Mvc\Model\Validator\InclusionIn,
Phalcon\Mvc\Model\Validator\Uniqueness;
class Robots extends \Phalcon\Mvc\Model
{
public function validation()
{
$this->validate(new InclusionIn(
array(
"field" => "type",
"domain" => array("Mechanical", "Virtual")
)
));
$this->validate(new Uniqueness(
array(
"field" => "name",
"message" => "The robot name must be unique"
)
));
return $this->validationHasFailed() != true;
}
}
http://docs.phalconphp.com/en/1.2.6/reference/models.html#validating-data-integrity
models also have events, so you can add any logic you need in these functions:
http://docs.phalconphp.com/en/1.2.6/reference/models.html#events-and-events-manager
I would like to use forms for CRUD as they are very dynamic and reusable.
You can achieve that in forms using options.
You can pass additional options to form and act like a scenario.
You can check Form constructor here
https://docs.phalconphp.com/en/latest/api/Phalcon_Forms_Form.html
In your controller you can pass $options
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function insertAction()
{
$options = array();
$options['scenario'] = 'insert';
$myForm = new MyForm(null, $options);
if($this->request->hasPost('insert')) {
// this will be our model
$profile = new Profile();
// we will bind model to form to copy all valid data and check validations of forms
if($myForm->isValid($_POST, $profile)) {
$profile->save();
}
else {
echo "<pre/>";print_r($myForm->getMessages());exit();
}
}
}
public function updateAction()
{
$options = array();
$options['scenario'] = 'update';
$myForm = new MyForm(null, $options);
}
}
And your form should look like something this
<?php
// elements
use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
// validators
use Phalcon\Validation\Validator\PresenceOf;
class MyForm extends Form {
public function initialize($entity = null, $options = null) {
$name = new Text('first_name');
$this->add($name);
if($options['scenario'] == 'insert') {
// at the insertion time name is required
$name->addValidator(new PresenceOf(array('message' => 'Name is required.')));
}
else {
// at the update time name is not required
// as well you can add more additional validations
}
}
}
now you can add multiple scenarios and act based on scenarios.

How to ajaxify Zend_Form validation

I have a Zend_Form subclass. Some elements are set to belong to arrays.
class My_Form extends Zend_Form {
public function __construct() {
$elem = $this->createElement('text','PROJECT_NAME',
array(
'required' => true
));
$elem->setBelongsTo('project');
$this->addElement($elem);
$elem = $this->createElement(
'text','PLANNED_END_DATE',
array(
'required' => true
)
);
$elem->setBelongsTo('project');
$elem->addValidator(new Zend_Validate_Date(array('format'=>'yyyy-MM-dd')));
$this->addElement($elem);
//and so on
}
}
I have a universal validation controller which does create the form and checks for errors, and returns them in json format:
class ValidateController extends Zend_Controller_Action
{
public function formAction()
{
$params = $this->_getAllParams();
if (isset($params['_frm'])) {
$formName = detect_the_form_class($params['_frm']);
if (class_exists($formName)) {
$form = new $formName();
if ($form instanceof Zend_Form) {
$result = $form->isValidPartial($params);
$messages = $form->getMessages();
$this->getResponse()
->setHeader('Content-Type', 'application/json')
->setBody(json_encode(array(
'result' => $result,
'messages' => $messages
)));
} else {
$this->getResponse()->setHttpResponseCode(500);
}
}
}
}
}
This controller works great for non-array forms, but the form I now need to validate hase arrays, eg elements with name 'project[PROJECT_NAME]'.
But the $form->getMessages() returns messages indexed with base name of elements, without array prefix.
The actual result is:
{ result: false,
messages: {
PROJECT_NAME: {isEmpty: "Value is required"},
PROJECT_END_DATE: {isEmpty: "Value is required"}
}
}
The result I need is:
{ result: false,
messages: {
"project[PROJECT_NAME]": {isEmpty: "Value is required"},
"project[PROJECT_END_DATE]": {isEmpty: "Value is required"}
}
}
or something similar, so I can find the element the validation message is for.
Any ideas?
For subforms use Zend_Form_Subform class:
class My_Form extends Zend_Form
{
public function init()
{
$subForm = new Zend_Form_SubForm();
$elem = $this->createElement('text', 'PROJECT_NAME', array(
'required' => true
));
$subForm->addElement($elem);
$elem = $this->createElement('text', 'PLANNED_END_DATE', array(
'required' => true
));
$subForm->addElement($elem);
$this->addSubForm($subForm, 'project');
}
}
Response:
{
"project":{
"PROJECT_NAME":{"isEmpty":"Value is required and can't be empty"},
"PLANNED_END_DATE":{"isEmpty":"Value is required and can't be empty"}
}
}
For form config it is recommended to use init() method.
For json response you can use build-in action helper:
$this->_helper->json($form->getMessages());

Resources