Getting unknown property error in Yii2 - yii2

I know there are some answers related to this, but I couldn't find any useful ones.
I am getting this error: Getting unknown property: app\models\Employees::holidays and I've no idea what I'm doing wrong.
Could someone help me out to solve this?
This is my model code:
<?php
namespace app\models;
use Yii;
use yii\data\ActiveDataProvider;
use yii\db\ActiveRecord;
/**
* This is the model class for table "employee".
*
* #property integer $id
* #property string $name
* #property string $surname
* #property string $employment_date
*/
class Employee extends ActiveRecord
{
/** #const SCENARIO_CREATE scenario - create */
const SCENARIO_CREATE = 'create';
/** #const SCENARIO_EDIT scenario - edit */
const SCENARIO_EDIT = 'edit';
/** #const SCENARIO_SEARCH scenario - search */
const SCENARIO_SEARCH = 'search';
/** #const HOLIDAYS_PER_WORK_DAY Earned holidays per one working day. */
const HOLIDAYS_PER_WORK_DAY = 0.4;
/** #var integer $holidays calculated holidays field used in list. */
public $holidays;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'employee';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
// id
['id', 'required', 'except' => self::SCENARIO_SEARCH],
['id', 'integer'],
// name
['name', 'required'],
['name', 'string', 'max' => 255],
// surname
['surname', 'required'],
['surname', 'string', 'max' => 255],
// employment date
['employment_date', 'required'],
['employment_date', 'match',
'pattern' => '/^([1][9]|[2][0])[0-9]{2}[-](0[1-9]|1[0-2])[-](0[1-9]|[1-2][0-9]|3[0-1])$/',
'message' => Yii::t('app',
'Neteisingas datos formatas (Formatas: YYYY-MM-DD)'),
],
['employment_date', 'date',
'format' => 'php:Y-m-d',
'max' => time(),
'tooBig' => Yii::t('app', 'Blogai įvesta data. Vėliausia galima data: ' . date('Y-m-d'))
],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => Yii::t('app', 'ID'),
'name' => Yii::t('app', 'Darbuotojo vardas'),
'surname' => Yii::t('app', 'Darbuotojo pavardė'),
'employment_date' => Yii::t('app', 'Įdarbinimo data'),
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
return [
self::SCENARIO_CREATE => [
'name',
'surname',
'employment_date',
],
self::SCENARIO_EDIT => [
'id',
'name',
'surname',
'employment_date',
],
self::SCENARIO_SEARCH => [
'id',
'name',
'surname',
'employment_date',
'holidays',
],
];
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$holidays = 'floor(datediff(curdate(), employment_date) * ' .
Employee::HOLIDAYS_PER_WORK_DAY . ')';
$query = Employees::find()->select([
$this->tableName() . '.id',
$this->tableName() . '.name',
$this->tableName() . '.surname',
$this->tableName() . '.employment_date',
$holidays . ' as holidays',
]);
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => [
'defaultOrder' => ['name' => SORT_ASC],
],
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'employment_date' => $this->employment_date,
]);
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'surname', $this->surname]);
return $dataProvider;
}
}
My index.php file:
<?php
use app\models\EmployeeSearch;
use yii\data\ActiveDataProvider;
use yii\grid\GridView;
use yii\helpers\Html;
use yii\web\View;
use yii\widgets\Pjax;
/* #var $this View */
/* #var $searchModel EmployeeSearch */
/* #var $dataProvider ActiveDataProvider */
$this->title = Yii::t('app', 'Employees');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="employee-index">
<h1><?= Html::encode($this->title) ?></h1>
<?php // echo $this->render('_search', ['model' => $searchModel]); ?>
<p>
<?= Html::a(Yii::t('app', 'Create Employee'), ['create'], ['class' => 'btn btn-success']) ?>
</p>
<?php Pjax::begin(); ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'id',
'name',
'surname',
'employment_date',
'holidays',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
<?php Pjax::end(); ?></div>
I know it's a quite long, but I don't know how much of the code I should include.
I think this line holidays in index php file is causing the error.

Add this on your rule .
['holidays', 'integer']
And use this $this->holidays during search.

There is something strange with your code:
My assumption is that you have 2 different models:
"Employee" and "Employees" in your app/models directory.
You are using "Employees" model in the search() of "Employee". For this to work you need to define:
public $holidays;
in your "Employees" model as your error suggests.
But my guess is that you wanted to use maybe "Employee" model for the search(). If this is the case, change the "Employees" to "Employee" in that function:
$query = Employee::find()...

you must comment all your index.php file and run your code. if you have not any error it means error is in index.php page.
then you must uncomment lines one by one and after each uncomment run your project.when you have error it means that line is one of your errors. then keep comment that line and uncomment other one by one and repeat that task to end of file.
in this way you can find your error.

Add the property for holidays in the model before class started:
#property string $holidays
Then it will work.

Related

Yii2 - Two models in one form - Values are not saved in the second model

I have a model called Patientinformation.php and a model called AlternativeInformation.php. I would like to use the two models in the patientinformation_form.php
Patientinformation model:
- id (PK)
- patient_id
- patient_initials
- collection_site
- cancer
- study (FK to study model)
AlternativeInformation model:
- alternative_id (PK)
- patients_patient_id (FK to id of the Patientinformation model)
- alternative_study (FK to study model)
- alternative_patient_id
I have updated the Patientinformation controller as well as the create.php file and the _form.php file.
If I enter values in the Patientinformation form.php, I am redirected to the Patientinformation view site. The values of the Patientinformation model (id, patient_id, patient_initials, collection_site, cancer and study) are saved in the database. However, the values of the AlternativeInformation model (alternative_study and alternative_patient_id) are not saved in the database.
This is the PatientinformationController.php file:
<?php
namespace app\controllers;
use Yii;
use app\models\Patientinformation;
use app\models\AlternativeInformation;
use app\models\PatientinformationSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* Creates a new Patientinformation model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new Patientinformation();
$modelAlternative = new AlternativeInformation();
if ($model->load(Yii::$app->request->post()) && $modelAlternative->load(Yii::$app->request->post())){
$model->save();
$modelAlternative->patients_patient_id = $model->id;
$modelAlternative->save();
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', [
'model' => $model,
'modelAlternative' => $modelAlternative,
]);
}
This is the create.php file:
<?php
use yii\helpers\Html;
/* #var $this yii\web\View */
/* #var $model app\models\Patientinformation */
/* #var $modelAlternative app\models\AlternativeInformation */
$this->title = 'Add patient';
$this->params['breadcrumbs'][] = ['label' => 'Patients', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="patientinformation-create">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
'modelAlternative' => $modelAlternative,
]) ?>
</div>
And this is the _form.php file:
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;
use app\models\CollectionSiteInformation;
use app\models\AlternativeInformation;
use app\models\StudyInformation;
use app\models\CancerInformation;
use unclead\multipleinput\MultipleInput;
use kartik\select2\Select2;
/* #var $this yii\web\View */
/* #var $model app\models\Patientinformation */
/* #var $modelAlternative app\models\AlternativeInformation */
/* #var $form yii\widgets\ActiveForm */
?>
<div class="patientinformation-form">
<?php $form = ActiveForm::begin(); ?>
<p>
Please note that all fields marked with an asterisk (<font color="red">*</font>) are required.
</p>
<?= $form->field($model, 'study')->widget(Select2::classname(), [
'data' => ArrayHelper::map(StudyInformation::find()->all(),'id','study'),
'language' => 'en',
'options' => ['placeholder' => 'Please select a study'],
]);
?>
<?= $form->field($model, 'patient_id')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'patient_initials')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'collection_site')->widget(Select2::classname(), [
'data' => ArrayHelper::map(CollectionSiteInformation::find()->all(),'id','collection_site'),
'language' => 'en',
'options' => ['placeholder' => 'Please select a collection site'],
]);
?>
<?= $form->field($model, 'cancer')->widget(Select2::classname(), [
'data' => ArrayHelper::map(CancerInformation::find()->all(),'id','cancer'),
'language' => 'en',
'options' => ['placeholder' => 'Please select a cancer type'],
]);
?>
<?= $form->field($modelAlternative,'alternative_study')->widget(MultipleInput::class,[
'max' => 6,
'allowEmptyList' => false,
'enableGuessTitle' => true,
'columns' => [
[
'name' => 'alternative_study',
'type' => 'dropDownList',
'title' => 'Alternative Study',
'items' => ArrayHelper::map(studyInformation::find()->asArray()->all (),'id','study'),
'options' => ['prompt' => 'Please select an alternative study if applicable'],
],
]
])
->label(false);
?>
<?= $form->field($modelAlternative,'alternative_patient_id')->widget(MultipleInput::class,[
'max' => 6,
'allowEmptyList' => false,
'enableGuessTitle' => true,
'columns' => [
[
'name' => 'alternative_patient_id',
'title' => 'Alternative patient ID',
'options' => ['prompt' => 'Please select an alternative study if applicable'],
],
]
])
->label(false);
?>
<div class="form-group">
<?= Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
If I don`t use unclead\multipleinput\MultipleInput in my _form.php, all the values of AlternativeInformation model (alternative_study and alternative_patient_id) are saved into the model/database.
So I assume that I don`t pass any items to the MultipleInput widget.
This is the AlternativeInformation model file:
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['patients_patient_id', 'alternative_study'], 'required'],
[['patients_patient_id', 'alternative_study'], 'integer'],
[['alternative_patient_id'], 'string', 'max' => 265],
[['patients_patient_id'], 'exist', 'skipOnError' => true, 'targetClass' => PatientInformation::className(), 'targetAttribute' => ['patients_patient_id' => 'id']],
[['alternative_study'], 'exist', 'skipOnError' => true, 'targetClass' => StudyInformation::className(), 'targetAttribute' => ['alternative_study' => 'id']],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'alternative_id' => 'Alternative ID',
'patients_patient_id' => 'Patients Patient ID',
'alternative_study' => 'Alternative Study',
'alternative_patient_id' => 'Alternative Patient ID',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getPatientsPatient()
{
return $this->hasOne(PatientInformation::className(), ['id' => 'patients_patient_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAlternativeStudy()
{
return $this->hasOne(StudyInformation::className(), ['id' => 'alternative_study']);
}
}
Does anyone have any suggestions how this issue can be solved? How will be the values of "alternative_study" and "alternative_patient_id" saved into the AlternativeInformation model if using the MultipleInput widget?
I really need the option in the form to enter no, one or several alternative studies as well as alternative patient ids.
I really appreciate any help how I can solve this issue.
Thanks :)
You are probably getting some validation errors that you can't see because you are redirecting to the view action even if the AlternativeInformation model has validation errors.
The first step towards finding a solution would be making sure that, if AlternativeInformation doesn't save you show the form again, that way you will see the validation errors.
Update your controller code to this:
public function actionCreate()
{
$model = new Patientinformation();
$modelAlternative = new AlternativeInformation();
if ($model->load(Yii::$app->request->post())
&& $modelAlternative->load(Yii::$app->request->post())
&& $model->save()) {
$modelAlternative->patients_patient_id = $model->id;
if ($modelAlternative->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
}
return $this->render('create', [
'model' => $model,
'modelAlternative' => $modelAlternative,
]);
}
That should help you find the problem, it will also give your users information about any future errors that may happen.
The validation problem may be on alternative_patient_id, it seems that you are not passing any items to the MultipleInput widget.
Update your controller with the following code.
public function actionCreate()
{
$model = new Patientinformation();
$modelAlternative = new AlternativeInformation();
if ($model->load(Yii::$app->request->post()) && $modelAlternative->load(Yii::$app->request->post())){
if($model->save()){
$modelAlternative->patients_patient_id = $model->id;
$modelAlternative->save();
return $this->redirect(['view', 'id' => $model->id]);
}
}
return $this->render('create', [
'model' => $model,
'modelAlternative' => $modelAlternative,
]);
}

Yii2 Codeception Functional tests - Click on OK button of confirm dialog

I'm a newer in Yii2 and programming tests.
I'm using Codeception for testing.
Summary:
In one of my tests I have to click on OK button of a confirm dialog. In order to do this, I have tried:
$I->click('OK');
$I->acceptPopup();
Neither of them work.
Details:
I'm using ActiveRecord to deal with a table of products ('productos'), and I used Gii to generate scripts.
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "productos".
*
* #property int $id
* #property string $codigo
* #property string $descripcion
* #property double $cantidad
* #property double $precio
* #property string $fefecto
*
* #property DetallesPedido[] $detallesPedidos
*/
class Productos extends \yii\db\ActiveRecord
{
/**
* {#inheritdoc}
*/
public static function tableName()
{
return 'productos';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['codigo', 'descripcion', 'precio', 'fefecto'], 'required'],
[['cantidad', 'precio'], 'number'],
[['fefecto'], 'date', 'format'=>'yyyy-M-d'],
[['fefecto'], 'safe'],
[['codigo'], 'string', 'max' => 10],
[['descripcion'], 'string', 'max' => 60],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'codigo' => 'Codigo',
'descripcion' => 'Descripcion',
'cantidad' => 'Cantidad',
'precio' => 'Precio',
'fefecto' => 'Fecha Alta',
];
}
}
The script for the view asociated to Productos.php is:
use yii\helpers\Html;
use yii\widgets\DetailView;
/* #var $this yii\web\View */
/* #var $model app\models\Productos */
$this->title = $model->id . ' - ' . $model->codigo;
$this->params['breadcrumbs'][] = ['label' => 'Productos', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="productos-view">
<h1><?= Html::encode($this->title) ?></h1>
<p>
<?= Html::a('Update', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
<?= Html::a('Delete', ['delete', 'id' => $model->id], [
'class' => 'btn btn-danger',
'data' => [
'confirm' => 'Are you sure you want to delete this item?',
'method' => 'post',
],
]) ?>
</p>
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'codigo',
'descripcion',
'cantidad',
'precio',
'fefecto',
],
]) ?>
</div>
The script for functional test:
<?php
use app\models\Productos;
class ProductosCest
{
public function _before(\FunctionalTester $I)
{
//Deleting all products from database
Productos::deleteAll();
$I->amLoggedInAs(100);//Logarse
$I->amOnRoute('productos/index');
// Loading product on database
$I->haveRecord(Productos::className(), [
'id' => 1,
'codigo' => 'PA01',
'descripcion' => 'Paleta de acero triangular de 20 cm',
'cantidad' => 1,
'precio' => 10.53,
'fefecto' => '2017-03-12',
]);
}
public function _after(\FunctionalTester $I)
{
$I->click(['class' => 'btn-link']);//Logout
//Deleting all products from database
Productos::deleteAll();
}
public function deleteProducto(\FunctionalTester $I)
{
$I->amGoingTo('delete a product');
$I->amOnRoute('productos/delete', ['id' => 1]); //Click delete button from id=1
//Pulsar el botón Aceptar
/*
$I->performOn('.confirm', \Codeception\Util\ActionSequence::build()
->see('Are you sure you want to delete this item?')
->click('Aceptar')
);
*/
$I->acceptPopup();
$I->expect('be in the index product view');
$I->see('Productos', 'h1');
$I->expect('the product is not in the index product view');
$I->dontSee('Paleta de acero triangular de 20 cm');
}
}
When I run tests, I get:
There was 1 failure:
1) ProductosCest: Delete producto
Test tests/functional/ProductosCest.php:deleteProducto
Step Click {"class":"btn-link"}
Fail Link or Button by name or CSS or XPath element with class 'btn-link' was not found.
Scenario Steps:
$I->click({"class":"btn-link"}) at tests/functional/ProductosCest.php:28
$I->amOnRoute("productos/delete",{"id":1}) at tests/functional/ProductosCest.php:132
// I am going to delete a product
$I->haveRecord("app\models\Productos",{"id":1,"codigo":"PA01","descripc...}) at tests/functional/ProductosCest.php:17
$I->amOnRoute("productos/index") at tests/functional/ProductosCest.php:13
$I->amLoggedInAs(100) at tests/functional/ProductosCest.php:12
As you can see, there is a problem in line:
$I->acceptPopup();
because message 'be in the index product view' does not appear in the test log.
Screenshots:
View products
Confirm Dialog
You use Yii2 module in functional tests, right?
This module does not execute javascript code, so you won't get confirmation dialog.
In order to test confirmation dialog, you must use WebDriver module, which is used in acceptance suite usually.
acceptPopup method only works with native popup windows, as created by window.alert, window.confirm or window.prompt.
If you use a modal window (it looks like you do), you must use click method to click it.

Trying to get property of non-object in action column in yii2

I've customized the query in FrameSearch Model and build my query with findbysql. I'm also trying to customize the action column. My gridview looks fine except the action Column.
Getting error -
FrameSearch Model Code -
<?php
namespace frontend\modules\framestock\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use frontend\modules\framestock\models\Frame;
use frontend\modules\framestock\models\Poitemframe;
use yii\db\Query;
use yii\db\Command;
/**
* FrameSearch represents the model behind the search form about `frontend\modules\framestock\models\Frame`.
*/
class FrameSearch extends Frame
{
public $purchase;
/**
* #inheritdoc
*/
public function rules()
{
return [
[['f_id'], 'integer'],
[['f_brand', 'f_name','purchase'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$subQuery1 = (new Query())->select(['poi_framename','sum(poi_qty) as purchased'])->from ('poitemframe')->join('LEFT JOIN','Frame','frame.f_name = poitemframe.poi_framename')->groupby('poi_framename');
$subQuery2 = (new Query())->select(['o_frame','count(o_frame) as sold'])->from ('orders')->join('LEFT JOIN','Frame','frame.f_name = orders.o_frame')->groupby('o_frame');
$query = (new Query())->select(['f_brand','f_name','COALESCE(pt.purchased,0)as purchased' , 'COALESCE(st.sold,0) as sold', '(COALESCE(pt.purchased,0) - COALESCE(st.sold,0)) as stock'])->from ('frame')->leftJoin(['pt' => $subQuery1],'pt.poi_framename = frame.f_name')->leftJoin(['st' => $subQuery2],'st.o_frame = frame.f_name');
// ->joinWith(['purchase']);
// $items = $query
// ->select([
// 'f_brand',
// 'f_name',
// 'sum(poitemframe.poi_qty) as purchased',
// ])
// //->where(['stock.product_id'=>3])
// ->groupBy('f_name')
// ->all();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'f_id' => $this->f_id,
]);
$query->andFilterWhere(['like', 'f_brand', $this->f_brand])
->andFilterWhere(['like', 'f_name', $this->f_name]);
return $dataProvider;
}
}
index.php
<?php
use yii\helpers\Html;
use yii\grid\GridView;
use yii\db\Query;
use yii\db\Command;
/* #var $this yii\web\View */
/* #var $searchModel frontend\modules\framestock\models\FrameSearch */
/* #var $dataProvider yii\data\ActiveDataProvider */
$this->title = 'Frames';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="frame-index">
<h1><?= Html::encode($this->title) ?></h1>
<?php // echo $this->render('_search', ['model' => $searchModel]); ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'f_brand',
'f_name',
'purchased',
'sold',
'stock',
[
'class' => 'yii\grid\ActionColumn',
'template' => '{ledger}',
'buttons' => [
'ledger' => function ($url, $model) {
return Html::a(
'<span class="glyphicon glyphicon-eye-open"></span>',
['/frame/ledger', 'f_name' => $model->f_name],
[
'title' => 'Ledger',
'data-pjax' => '0',
]
);
},
],
],
],
]); ?>
</div>
update
After adding exit;
Please tell me how to resolve this. I want to get the f_name from the action column button which I'll pass to another page to filter a grid.
I usually recommend to use ArrayHelper::getValue .
For example:
ArrayHelper::getValue($model, 'f_name')
will work when $model is some object (Model or ActiveRecord), array or empty (=null).
Moreover, it has 3nd argument for default value:
ArrayHelper::getValue($model, 'f_name', 'value that returned when attribute not found in $model')

Yii2 Select2 - selected value on update - junction table (many to many)

I am successfully inserting new rows in my junction table (userlocations) on action create and I successfully update them on action update but the problem is on ation update the location_id field is always empty. It should retrieve the location_id's from userlocations table and populate the field on update but it doesnt.
Database: http://i.stack.imgur.com/JFjdz.png
UserController:
<?php
namespace backend\controllers;
use Yii;
use backend\models\User;
use backend\models\UserSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\helpers\ArrayHelper;
use backend\models\Locations;
use backend\models\Userlocations;
class UserController extends Controller
{
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
/**
* Lists all User models.
* #return mixed
*/
public function actionIndex()
{
$searchModel = new UserSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
/**
* Displays a single User model.
* #param integer $id
* #return mixed
*/
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
/**
* Creates a new User model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new User();
$locations = ArrayHelper::map(Locations::find()->all(), 'id', 'name');
$userlocations = new Userlocations();
if ($model->load(Yii::$app->request->post()) ) {
$model->setPassword($model->password);
$model->generateAuthKey();
$userlocations->load(Yii::$app->request->post());
if ($model->save() && !empty($userlocations->location_id)){
foreach ($userlocations->location_id as $location_id) {
$userlocations = new Userlocations();
$userlocations->setAttributes([
'location_id' => $location_id,
'user_id' => $model->id,
]);
$userlocations->save();
}
}
return $this->redirect(['user/index']);
} else {
return $this->render('create', [
'model' => $model,
'locations' => $locations,
'userlocations' => $userlocations,
]);
}
}
/**
* Updates an existing User model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
$locations = ArrayHelper::map(Locations::find()->all(), 'id', 'name');
$userlocations = new Userlocations();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Userlocations::deleteAll(['user_id' => $id]);
$userlocations->load(Yii::$app->request->post());
if (!empty($userlocations->location_id)){
foreach ($userlocations->location_id as $location_id) {
$userlocations = new Userlocations();
$userlocations->setAttributes([
'location_id' => $location_id,
'user_id' => $model->id,
]);
$userlocations->save();
}
}
return $this->redirect(['user/index']);
} else {
return $this->render('update', [
'model' => $model,
'locations' => $locations,
'userlocations' => $userlocations,
]);
}
}
/**
* Deletes an existing User model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param integer $id
* #return mixed
*/
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
/**
* Finds the User model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param integer $id
* #return User the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = User::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}
User model:
class User extends \common\models\User
{
public $password;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'User';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['username', 'password'], 'required'],
[['status', 'created_at', 'updated_at'], 'integer'],
[['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
[['auth_key'], 'string', 'max' => 32],
[['username'], 'unique', 'message' => 'Username already taken!'],
[['email'], 'unique'],
[['password_reset_token'], 'unique'],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'username' => 'Username',
'auth_key' => 'Auth Key',
'password_hash' => 'Password Hash',
'password_reset_token' => 'Password Reset Token',
'email' => 'Email',
'status' => 'Status',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
}
My form:
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;
use kartik\select2\Select2;
use backend\models\Locations;
?>
<div class="user-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'username')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'password')->passwordInput(['maxlength' => true]) ?>
<?= $form->field($model, 'email')->textInput(['maxlength' => true]) ?>
<?= $form->field($userlocations, 'location_id')->widget(Select2::classname(), [
'data' => ArrayHelper::map(Locations::find()->all(), 'id', 'name'),
'size' => Select2::MEDIUM,
'options' => ['placeholder' => 'Select a location ...', 'multiple' => true],
'pluginOptions' => [
'allowClear' => true,
],
]); ?>
<?= $form->field($model, 'status')->dropDownList(['10' => 'Active', '0' => 'Inactive']) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
For multiple location select, use the plugin code as given below.
<?= Select2::widget([
'name' => 'Userlocations[location_id]',
'value' => $location_ids, // initial value
'data' => ArrayHelper::map(Locations::find()->all(), 'id', 'name'),
'options' => ['placeholder' => 'Select your locations...', 'multiple' => true],
'pluginOptions' => [
'tags' => true,
'maximumInputLength' => 10
],
]); ?>
$location_ids will be the location array you have previously selected during creation time.Also remember when you are making changes for more than one table,make sure you do it in a transaction.

Sort and search column when I'm querying with findbysql in yii2

I'm searching four tables and joined them and got the output I want. But unable to sort or filter the output. Please tell me how I can search it by district or a sell range or collection range.
PartiesSearch model is -
<?php
namespace frontend\modules\districtreport\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use frontend\modules\districtreport\models\Parties;
use frontend\modules\districtreport\models\Bills;
use frontend\modules\districtreport\models\Payment;
use yii\db\Query;
use yii\db\Command;
$query = \Yii::$app->db;
/**
* PartiesSearch represents the model behind the search form about `frontend\modules\districtreport\models\Parties`.
*/
class PartiesSearch extends Parties
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['party_id'], 'integer'],
[['parties_partyname', 'address', 'parties_district', 'name_manager', 'transport', 'dlno', 'instruction', 'con', 'district','sale','sell','collection'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$sql = 'select
tsell.district as district,
tsell.totalsale as sell,
coalesce(tcollection.collection,0) as collection
from
(SELECT
district,
coalesce(sell.sale,0) as totalsale
FROM `districts`
left join
(SELECT
parties_district,
billdate,
sum(billamount) as sale
FROM `bills`
left join parties on bills.bills_partyname = parties.parties_partyname
group by parties_district) as sell
on sell.parties_district = districts.district) as tsell
left join
(SELECT
parties_district,
payment_date,
COALESCE(sum(payment_amount),0) as collection
FROM `payment`
left join parties on payment.payment_partyname = parties.parties_partyname
group by parties_district) as tcollection
on tsell.district = tcollection.parties_district';
$query = Parties::findBySql($sql);
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
//'sort'=> ['defaultOrder' => ['district'=>SORT_DESC]]
]);
$dataProvider->setSort([
'attributes' => [
'sell' => [
'asc' => ['sell' => SORT_ASC],
'desc' => ['sell' => SORT_DESC],
'label' => 'Sell'
],
'collection' => [
'asc' => ['collection' => SORT_ASC],
'desc' => ['collection' => SORT_DESC],
'label' => 'Collection'
],
'district' => [
'asc' => ['tsell.district' => SORT_ASC],
'desc' => ['tsell.district' => SORT_DESC],
'label' => 'District'
]
]
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'party_id' => $this->party_id,
]);
$query->andFilterWhere(['like', 'parties_partyname', $this->parties_partyname])
->andFilterWhere(['like', 'address', $this->address])
->andFilterWhere(['like', 'parties_district', $this->parties_district])
->andFilterWhere(['like', 'name_manager', $this->name_manager])
->andFilterWhere(['like', 'transport', $this->transport])
->andFilterWhere(['like', 'dlno', $this->dlno])
->andFilterWhere(['like', 'instruction', $this->instruction])
->andFilterWhere(['like', 'con', $this->con])
->andFilterWhere(['like', 'sell', $this->sell])
->andFilterWhere(['like', 'collection', $this->collection])
->andFilterWhere(['like', 'district', $this->district]);
return $dataProvider;
}
}
Parties Model
<?php
namespace frontend\modules\districtreport\models;
use Yii;
/**
* This is the model class for table "parties".
*
* #property integer $party_id
* #property string $parties_partyname
* #property string $address
* #property string $parties_district
* #property string $name_manager
* #property string $transport
* #property string $dlno
* #property string $instruction
* #property string $con
*/
class Parties extends \yii\db\ActiveRecord
{
public $sale;
public $district;
public $sell;
public $collection;
public $bills;
public $partyname;
public $billdate;
//public $sale;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'parties';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['parties_partyname', 'parties_district', 'name_manager'], 'required'],
[['parties_partyname'], 'string', 'max' => 60],
[['address', 'instruction'], 'string', 'max' => 100],
[['parties_district'], 'string', 'max' => 20],
[['name_manager', 'transport', 'dlno'], 'string', 'max' => 30],
[['con'], 'string', 'max' => 10],
[['parties_partyname'], 'unique'],
[['name_manager'], 'exist', 'skipOnError' => true, 'targetClass' => Managers::className(), 'targetAttribute' => ['name_manager' => 'manager_managername']],
[['con'], 'exist', 'skipOnError' => true, 'targetClass' => Console::className(), 'targetAttribute' => ['con' => 'console']],
[['parties_district'], 'exist', 'skipOnError' => true, 'targetClass' => Districts::className(), 'targetAttribute' => ['parties_district' => 'district']],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'party_id' => 'Party ID',
'parties_partyname' => 'Parties Partyname',
'address' => 'Address',
'parties_district' => 'Parties District',
'name_manager' => 'Name Manager',
'transport' => 'Transport',
'dlno' => 'Dlno',
'instruction' => 'Instruction',
'con' => 'Con',
];
}
public function getDistricts()
{
return $this->hasOne(Districts::className(), ['district' => 'parties_district']);
}
public function getBills()
{
return $this->hasMany(Bills::className(), ['bills_partyname' => 'parties_partyname']);
}
public function getPayment()
{
return $this->hasMany(Payment::className(), ['payment_partyname' => 'parties_partyname']);
}
}
index.php
<?php
use yii\helpers\Html;
use kartik\grid\GridView;
//use kartik\widgets\DatePicker;
use kartik\daterange\DateRangePicker;
use kartik\form\ActiveForm;
use dosamigos\datepicker\DatePicker;
use frontend\modules\districtreport\models\ExpartiesSearch;
/* #var $this yii\web\View */
/* #var $searchModel frontend\modules\districtreport\models\PartiesSearch */
/* #var $dataProvider yii\data\ActiveDataProvider */
$this->title = 'Parties';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="parties-index">
<h1><?= Html::encode($this->title) ?></h1>
<?php // echo $this->render('_search', ['model' => $searchModel]); ?>
<!-- <p>
<?= Html::a('Create Parties', ['create'], ['class' => 'btn btn-success']) ?>
</p> -->
<!-- <div class="custom-filter">
Date range:
<input name="start" />
<input name="end" />
</div> -->
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'export' => false,
'columns' => [
[
//['class' => 'yii\grid\SerialColumn'],
'class' => 'kartik\grid\ExpandRowColumn',
'value' => function($model, $key, $index, $column){
return GridView::ROW_COLLAPSED;
},
'detail' => function($model, $key, $index, $column){
$searchModel = new ExpartiesSearch();
$searchModel-> district = $model->district;
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return Yii::$app->controller->renderPartial('_exparties', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
},
],
//['class' => 'yii\grid\SerialColumn'],
'district',
// [
// 'attribute' => 'date',
// 'value' => 'tsell.date',
// 'filter' => \yii\jui\DatePicker::widget(['language' => 'ru', 'dateFormat' => 'dd-MM-yyyy']),
// 'format' => 'html',
// ],
'sell',
'collection',
//['class' => 'yii\grid\ActionColumn'],
],
]); ?>
</div>
In this picture we can see that though the sell sort is there but it's not sorting the data actually.
In picture 2 we can see that though the data assam is passed to the next level of kartik expandrow, it's not filtering.
According to yii2's docs you're using an ActiveDataProvider with an ActiveRecord inside. Here's the example docs page for your case.
I think what you want to do is to return $dataProvider->getModels() rather than just return $dataProvider. This will return presumably all rows, since I don't see a pagination mechanism (there could be a default pagination count).
For sorting, you could try sorting on the $query rather than the $dataProvider. Try something like:
$query->orderBy([
'tsell.sell' => SORT_ASC
]);
Setup a grid view with a data provider and filter model.
In your view:
<?= GridView::widget([
'dataProvider'=> $dataProvider,
'filterModel' => $searchModel,
'columns' => [
'parties_partyname',
'address',
'parties_district',
'name_manager',
'transport',
'dlno',
'instruction',
'con',
'district',
'sale',
'sell',
'collection'
]
]); ?>
In your controller (replace index with the name of your view file):
$searchModel = new \frontend\modules\districtreport\models\PartiesSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
Try removing $dataProvider->setSort() from your search model.
Also, in your detail of your gridview you are using the same variable name for both search models and data providers. They are two separate models so the variables should not be the same.
<?php
namespace frontend\modules\districtreport\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use frontend\modules\districtreport\models\Parties;
use frontend\modules\districtreport\models\Bills;
use frontend\modules\districtreport\models\Payment;
use yii\db\Query;
use yii\db\Command;
/**
* PartiesSearch represents the model behind the search form about `frontend\modules\districtreport\models\Parties`.
*/
class PartiesSearch extends Parties
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['party_id'], 'integer'],
[['parties_partyname', 'address', 'parties_district', 'name_manager', 'transport', 'dlno', 'instruction', 'con', 'district','sale','sell','collection'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$sql = 'select
tsell.district as district,
tsell.totalsale as sell,
coalesce(tcollection.collection,0) as collection
from
(SELECT
district,
coalesce(sell.sale,0) as totalsale
FROM `districts`
left join
(SELECT
parties_district,
billdate,
sum(billamount) as sale
FROM `bills`
left join parties on bills.bills_partyname = parties.parties_partyname
group by parties_district) as sell
on sell.parties_district = districts.district) as tsell
left join
(SELECT
parties_district,
payment_date,
COALESCE(sum(payment_amount),0) as collection
FROM `payment`
left join parties on payment.payment_partyname = parties.parties_partyname
group by parties_district) as tcollection
on tsell.district = tcollection.parties_district';
$query = Parties::findBySql($sql);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'party_id' => $this->party_id,
'parties_partyname' => $this->parties_partyname,
'address' => $this->address,
'parties_district' => $this->parties_district,
'name_manager' => $this->name_manager,
'transport' => $this->transport,
'dlno' => $this->dlno,
'instruction' => $this->instruction,
'con' => $this->con,
'sell' => $this->sell,
'collection' => $this->collection,
'district' => $this->district,
]);
return $dataProvider;
}
}
Simplified Search Model that works for me:
<?php
namespace common\models\event;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use common\models\Model;
class ModelSearch extends Model
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'name'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = Model::find()->indexBy('id');
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'name' => $this->name,
]);
return $dataProvider;
}
}
According to http://www.yiiframework.com/doc-2.0/yii-db-activerecord.html#findBySql%28 code with findbysql cannot be sorted or filtered.
However, it can be sorderd by building query following page - http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html
The query will be like -
$subQuery1 = (new Query())->select(['parties_district','billdate','sum(billamount) as sale'])->from ('parties')->join('LEFT JOIN','bills','bills.bills_partyname = parties.parties_partyname')->groupby('parties_district')->where('billdate != "NULL"');
$subQuery2 = (new Query())->select(['district','coalesce(sell.sale,0) as totalsale'])->from('districts')->leftJoin(['sell' => $subQuery1],'sell.parties_district = districts.district');
$subQuery3 = (new Query())->select(['parties_district','payment_date','COALESCE(sum(payment_amount),0) as collection'])->from('payment')->join('LEFT JOIN','parties','payment.payment_partyname = parties.parties_partyname')->groupby('parties_district');
$query = (new Query())->select(['tsell.district as district','tsell.totalsale as sell','coalesce(tcollection.collection,0) as collection'])->from(['tsell'=> $subQuery2])->leftJoin(['tcollection' => $subQuery3],'tcollection.parties_district = tsell.district');
Should anyone run into this issue all these years later: queries built with the findBySql method will be sortable (and pagination will work too, which doesn't with ActiveDataProvider + findBySql) if you pass them to an ArrayDataProvider instead of an ActiveDataProvider, like this:
$query = MyModel::findBySql("SELECT * FROM ...");
$dataProvider = new ArrayDataProvider([
'allModels' => $query->all(),
'sort' => [
'attributes' => [
'model_id',
'model_name'
],
'defaultOrder' => [
'model_name' => SORT_ASC
]
],
'pagination' => [
'pageSize' => 30
]
]);
The traditional filtering methods won't work. From the findBySql() docs:
Note that because the SQL statement is already specified, calling additional query modification methods (such as where(), order()) on the created yii\db\ActiveQuery instance will have no effect.
I've come up with a workaround, it requires slightly more work than using the interface methods, but for complex queries (which I assume is why you are trying to use findBySql) it works:
public function search($params)
{
$this->load($params);
$filters = [];
if ($this->model_id) $filters[] = ' "table"."model_id" = '.$this->model_id.' ';
if ($this->model_name) $filters[] = ' "table"."model_name" ILIKE \'%'.$this->model_name.'%\' ';
// ugly ifs can be put into a foreach if you don't need to customize each condition too much
$filterCondition = count($filters) > 0 ? ' AND ' . implode(' AND ', $filters) : ' ';
$query = MyModel::findBySql(
// your query here
.$filterCondition.
// group by, order by etc.
);
// assemble the ArrayDataProvider (see above)
return $dataProvider;
}
Yii version: 2.0.34, PHP version: 7.3.18, PostgreSQL version: 12.2