Yii2 Select only few columns from related model - yii2

In controller i have:
public function actionGetItems()
{
$model = new \app\models\WarehouseItems;
$items = $model->find()->with(['user'])->asArray()->all();
return $items;
}
In WarehouseItem model i have standard (created by gii) relation declaration:
public function getUser()
{
return $this->hasOne('\dektrium\user\models\User', ['user_id' => 'user_id']);
}
How can i control which column data do i get from "user" relation? I currently get all columns which is not good as that data is being sent to Angular in JSON format.
Right now i have to loop trough $items and filer out all columns i dont want to send.

You should simply modify the relation query like this :
$items = \app\models\WarehouseItems::find()->with([
'user' => function ($query) {
$query->select('id, col1, col2');
}
])->asArray()->all();
Read more : http://www.yiiframework.com/doc-2.0/yii-db-activequerytrait.html#with()-detail

Your code should go this way.
public function actionGetItems()
{
$items = \app\models\WarehouseItems::find()
->joinWith([
/*
*You need to use alias and then must select index key from parent table
*and foreign key from child table else your query will give an error as
*undefined index **relation_key**
*/
'user as u' => function($query){
$query->select(['u.user_id', 'u.col1', 'u.col2']);
}
])
->asArray()
->all();
return $items;
}

Inside WarehouseItem model
/**
* #return ActiveQuery
*/
public function getUser()
{
$query = User::find()
->select(['id', 'col1', 'col2'])
->where([
'id' => $this->user_id,
]);
/**
* Default hasOne, setup multiple for hasMany
* $query->multiple = true;
*/
return $query;
}

Related

can't update json column using Laravel eloquent model

I'm trying to test an update to an Eloquent model...
/** #test */
public function updates_to_json_fields_are_logged()
{
$data = json_encode(["json_key" => "old_value"]);
$individual = Individual::factory()->create([
"information" => $data
]);
json_decode($individual->information)->json_key = "new_value";
$individual->save();
echo(var_dump($individual));
$this->assertTrue(false);
}
information is a json column.
When I log $individual after saving it, the value of "information->json_key" is still "old_value". Can anyone tell me why?
To change the $individual object without fancy assign
/** #test */
public function updates_to_json_fields_are_logged()
{
$data = json_encode(["json_key" => "old_value"]);
$individual = Individual::factory()->create([
"information" => $data
]);
$decodedInformation = json_decode($individual->information);
$decodedInformation->json_key = "new_value";
$individual->information = json_encode($decodedInformation);
$individual->save();
echo(var_dump($individual));
$this->assertTrue(false);
}
You don't change original $individual object, but the result of json_decode().

Yii2: How to delete with relation 3 table on Yii2

I have the following tables and I want to click delete at Pak button which can delete all 3 tables relations.
How can I achieve that?
table Pak : id_pak, pak_name/////
table Church : id_church, church_name, id_pak/////
table Member : id_member, name_member, id_church////
public function actionDelete($id)
{
$this->findModel($id);
$select = Church::find()
->select('church_name')
->where(['id_pak' => $id])
->all();
$a3 = Church::find()
->select('id_church')
->where(['id_pak' => $id])
->all();
$select2 = Member::find()
->select('member_name')
->where(['id_church'=> $a3])
->all();
Church::find()->where(['id_pak' => $id])->one()->delete();
Pak::find()->where(['id_pak' =>$id])->one()->delete();
Member::find()->where(['id_church'=> $a3])->one()->delete();
return $this->redirect(['index','select'=>$select,'select2'=>$select2]);
}
Using Constraints with innoDB Engine
If you are using InnoDB and have added the constraints on delete cascade correctly and defined the respective relations in the models you don't need to worry about the related records in the other tables you just need to find the model in the Pak and delete it.
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
protected function findModel( $id ) {
if ( ($model = Pak::findOne ( $id )) !== null ) {
return $model;
}
throw new NotFoundHttpException ( 'The requested page does not exist.' );
}
Removing Manually
Or if you are not using innoDB or not using constraints for any reason then you can override the beforeDelete() in the ActiveRecord Model for Pak and remove all the child rows for the Pak Model in the Church and override beforeDelete() inside the Church to delete all child rows in Member model and return true from there to continue deleting the actual record in the Pak model
I assume that you have the following relations defined in the Pak model
public function getChurch(){
return $this->hasOne(Church::className(), ['id_pak'=>'id_pak']);
}
and the following inside the Church model
public function getMember(){
return $this->hasOne(Member::className(),['id_church'=>'id_church']);
}
Then override the beforeDelete() in the Pak model
public function beforeDelete() {
$this->church->delete();
return parent::beforeDelete ();
}
and override the beforeDelete() in the Church Model
public function beforeDelete() {
$this->member->delete();
return parent::beforeDelete ();
}
and in your actionDelete() just find the model and call delete
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
There is also a nice article on implementing recursiveDelete() method in a parent model here.
Following code may help you to solve your problem.
public function actionDelete($id)
{
$select = Church::find()
->select('church_name')
->where(['id_pak' => $id])
->all();
$a3 = Church::find()
->select('id_church')
->where(['id_pak' => $id])
->all();
$select2 = Member::find()
->select('member_name')
->where(['id_church'=> $a3])
->all();
// ---------- start ---------------
$park = Park::find()->where(['id_pak' => $id])->one();
if ( $park->delete() ){
Pak::deleteAll('id_pak = :id', [':id' => $id]);
foreach ($a3 as $value) {
Member::deleteAll('id_church = :id', [':id' => $value->id_church ]);
}
}
// ---------- end ---------------
return $this->redirect(['index','select'=>$select,'select2'=>$select2]);
}

Many to many relationships in Yii2

I am trying to create a many to many relationship between a profile and several fields within that profile including languages and specialities. I have looked at several implementations and understand that there are several extensions, but I am required to minimise usage of extensions.
I have created the proper migration...this is a skeleton and the user table is purely for logins and OAuth...so keys can be ignored. As you can see in my controller I don't really know the way forward at this point. My language model is for all intensive purposes static from this controller(It is controlled by an admin backend). What is working? If I make a couple modifications to the below code then the checkboxlist will display with the proper checked items that I manually added to the lookup table. Trying to modify the lookup table from code, I have been unable to do unless I populate the $languageModel with a findOne(knownPK), however that is not usable, because multiple checkboxes can be selected resulting in an array and the link command requires ActiveRecordInterface which is singular. Ideally I would like to simply use
$languageModel->load(Yii::$app->request->post(),$trainerModel->formName());
but that isn't working.
Additionally, is there a mechanism within the framework to remove lookups or is that done manually. Any help or insight would be helpful. Thank you in advance.
public function safeUp()
{
$this->createTable('student', [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
]);
$this->createTable('language', [
'id' => $this->primaryKey(),
'language' => $this->string(63),
]);
$this->createTable('student_language',[
'id' => $this->primaryKey(),
'language_id' => $this->integer(),
'student_id' => $this->integer()
]);
$this->addForeignKey('fk-student-language-language', 'student_language', 'language_id', 'language', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-language-student', 'student_language', 'student_id', 'student', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-user-user_id', 'student', 'user_id', 'user', 'id', 'CASCADE', 'CASCADE');
}
My student Active Record
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['student_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(Language::className(),['id'=>'language_id'])->viaTable('student_language',['student_id'=>'id']);
}
My Language Model
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['language_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(student::className(),['id'=>'student_id'])->viaTable('student_language',['language_id'=>'id']);
}
My Controller
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languageModel = ??????????????????
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->link('languages',$languageModel);
}
return $this->render('profile', ['model' => $studentModel]);
}
My View
<?= $form->field($model, 'languages')->checkboxList(\common\models\Language::find()->select(
['language', 'id'])->indexBy('id')->column(), ['prompt' => 'select Language']); ?>
This is my solution at this point, however I am interested to hear improvements.
Note: There is no error checking etc...just functional code.
I added this to the student activerecord class
public function linkMultiple( $name,Array $models){
studentLanguage::deleteAll(['student_id' =>$this->id]);
foreach($models as $model){
$this->link($name, $model);
}
return true;
}
Modified my Controller action to the following
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languages = Yii::$app->request->post('student')['languages'];
$languageModels = Language::findAll($languages);
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->linkMultiple('languages',$languageModels);
}
return $this->render('profile', ['model' => $studentModel]);
}

How to insert same data record to multiple table on Yii2

I'm using yii2-advanced. I've several table :
tb_user:(iduser(PK),username),
tb_profile:(id,iduser(FK)),
tb_status:(id,iduser(FK))
My question is how can i insert iduser(PK) from tb_user to iduser(FK) on tb_profile and tb_status after i push the signup button.
For a while i think i must to do some modification of bevahiours() function on User model and i found some error, or adding trigger syntax on the table ? (i think this is not a good ways).
Is there anyone who can help me, how to solve my problem ?
this is the User model before the modification :
<?php
namespace common\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%user}}';
}
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'created_at',
ActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
],
'value' => function () {return date('Y-m-d h:m:s');},
],
];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->getPrimaryKey();
}
}
?>
The Controller :
public function actionSignup()
{
$model = new SignupForm();
if ($model->load(Yii::$app->request->post())) {
if ($user = $model->signup()) {
if (Yii::$app->getUser()->login($user)) {
return $this->goHome();
}
}
}
return $this->render('signup', [
'model' => $model,
]);
}
I had similar situation in one of my project where i had 2 tables like user,user_image where user_id was foreign key to add the path.
For those kind of situation you can use either of following approach
1.Insert record in both table on click of signup button. You will have to write update action accordingly.
$user = new User();
$user->name = "John"
$user->email = "John#gmail.com"
//Add if any other fields in table
$user->save(); //save the record
$user_image = new UserImage();
$user_image->user_id = $user->id;
$user_image->image = "image path"
//Add any other images here
$user_image->save();//save the record
2.You can also call create action of UserImage and do the same. If you use this approach than you might also need to use any other unique column to find the id of that user and use it to insert new record,for example in my table email is unique column so i can write following code in UserImage and get the id
$user = User::findOne(['email' => 'john#gmail.com']);//this will return whole row
$user_image->user_id = $user->id;
$user_image->image = "image path"
//Add any other images here
$user_image->save();//save the record
And that way you can use the code as per it suits your need
Thank you

Yii2 - insert relational data with junction table, many-many connection

I have a problem with Yii2 (Stable).
I have a Content(PK:id) table, I have a Tag(PK:id) table, and I have a junction table called Content_Tag (PK:content_id, tag_id). I'd like to use it for tagging, like WP tags.
All controllers and models are created with gii.
I have two problems:
If I create a new content, I'd like to save some new tags to the Tag table via the Content_Tag table. How can I do that? With link()?
What if there are tags (I know the ids) in the tag table, I'd like to connect only with the Content table via the junction table, without inserting into the Tag table. How can I do this?
I don't want to write native SQL command, I'd like to use the Yii2 built in functions like link() or via() or viaTable().
Thanks for your help!
I created a behavior to help handle do this, basically you do:
$content = Content::findOne(1);
$tags = [Tag::findOne(2), Tag::findOne(3)];
$content->linkAll('tags', $tags, [], true, true);
You can get the behavior here:
https://github.com/cornernote/yii2-linkall
If you'd prefer to do it without the behavior, something like this:
// get the content model
$content = Content::findOne(1);
// get the new tags
$newTags = [Tag::findOne(2), Tag::findOne(3)];
// get the IDs of the new tags
$newTagIds = ArrayHelper::map($newTags, 'id', 'id');
// get the old tags
$oldTags = $post->tags;
// get the IDs of the old tags
$oldTagIds = ArrayHelper::map($oldTags, 'id', 'id');
// remove old tags
foreach ($oldTags as $oldTag) {
if (!in_array($oldTag->id, $newTagIds)) {
$content->unlink('tags', $oldTag, true);
}
}
// add new tags
foreach ($newTags as $newTag) {
if (!in_array($newTag->id, $oldTagIds)) {
$content->link('tags', $newTag);
}
}
If you created models using gii then you might seen in the model the relationship is done like:
/**
* #return \yii\db\ActiveQuery
*/
public function getContent()
{
return $this->hasMany(Content_Tag::className(), ['content_id' => 'id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getContent()
{
return $this->hasMany(Tag::className(), ['tag_id' => 'tag_id'])->viaTable('content_tag', ['content_id' => 'id']);
}
If you want to save in Content_Tag table based on Content and Tag table then in controller you can use:
public function actionCreate()
{
$model = new Tag();
$content = new Content();
$content_tag = new Content_tag();
if($model->load(Yii::$app->request->post()) && $model->save()){
$model->save(false);
$content_tag->tag_id = $model->id;
$content_tag->content_id = $model->content_id;
$content_tag->save(false);
if($model->save(false))
{
Yii::$app->getSession()->setFlash('success', 'Created successfully');
return $this->render('create',[
'model' => $model,
'content' => $content,
'content_tag' => $content_tag
]);
}
}
else
{
return $this->render('create', [
'model' => $model,
]);
}
}
You can use link() to save. I am also searching for that as I didn't use that.