Many-to-many relation in yii2 activedataprovider - php

I have many-to-many relation with three tables: Category, Product, ProductCategory.
Relation in Category:
public function getProductCategories()
{
return $this->hasMany(ProductCategory::className(), ['category_id' => 'id']);
}
Relation in Product:
public function getProductCategories()
{
return $this->hasMany(ProductCategory::ClassName(), ['product_id' => 'id']);
}
And in ProductCategory
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
public function getCategory()
{
return $this->hasOne(Category::className(), ['id' => 'category_id']);
}
In my Category Controller I used this code to show the products I need according to their category (one-to-many):
$cats = Category::findOne(['slug1'=>$slug1]);
$dataProvider = new ActiveDataProvider([
'query' => $query = Product::find()->where(['category_id' => $cats->id]),
'sort'=>array(
'defaultOrder'=>['id' => SORT_ASC],
),
'pagination' => [
'pageSize' => 9,
],
]);
So the question is how to make my ActiveDataProvider to get in query the many-to-many relation?

You can create two more relations like this
In category:
public function getProducts()
{
return $this->hasMany(Product::className(), ['id' => 'product_id'])->via("productCategories");
}
And in product:
public function getCategories()
{
return $this->hasMany(Category::ClassName(), ['id' => 'category_id'])->via("productCategories");
}
Then you can use it like this
$cats = Category::findOne(['slug1'=>$slug1]);
$dataProvider = new ActiveDataProvider([
'query' => $query = $cats->getProducts(),
'sort'=>array(
'defaultOrder'=>['id' => SORT_ASC],
),
'pagination' => [
'pageSize' => 9,
],
]);

Related

Laravel resource return empty array

I have resource where i get product data trough third table but having hard time make relationships work on models so it return empty array.
Logic
Product has many barcodes
Barcodes can have (belongsTo) damage
In damage we get product trough barcode table (we store barcode_id)
I also included fillable part of each column so you can see columns in database.
Code
Product model
class Product extends Model
{
protected $fillable = [
'name', 'slug', 'stock', 'cover', 'description', 'sku', 'price', 'discount',
];
public function barcodes()
{
return $this->hasMany(Barcode::class);
}
}
Barcode model
class Barcode extends Model
{
protected $fillable = [
'product_id', 'sku', 'serial_number', 'price', 'discount',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function damages()
{
return $this->hasMany(DamageProduct::class);
}
}
DamageProduct model
class DamageProduct extends Model
{
protected $fillable = [
'outlet_id', 'user_id', 'barcode_id', 'description',
];
public function barcode()
{
return $this->belongsTo(Barcode::class);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
DamageProductsResource resource
class DamageProductsResource extends JsonResource
{
public function toArray($request)
{
$arrayData = [
'id' => $this->id,
'outlet' => new OutletsResource($this->whenLoaded('outlet')),
'user' => new usersResource($this->whenLoaded('user')),
'barcode' => new BarcodeResource($this->whenLoaded('barcode')),
'description' => $this->description,
];
return $arrayData;
}
}
Result
Any idea?
Update
In case you need to see how BarcodeResource resource looks like here it is:
public function toArray($request)
{
$arrayNull = [
'id' => $this->id,
'product' => new ProductsResource($this->whenLoaded('product')),
'sku' => $this->sku,
'serial_number' => $this->serial_number ? (Int) $this->serial_number : null,
'price' => (Int) $this->price,
'discount' => $this->discount ? (Int) $this->discount : null,
];
}
I would say you simply forgot the return statement in your BarcodeResource
public function toArray($request)
{
$arrayNull = [
'id' => $this->id,
'product' => new ProductsResource($this->whenLoaded('product')),
'sku' => $this->sku,
'serial_number' => $this->serial_number ? (Int) $this->serial_number : null,
'price' => (Int) $this->price,
'discount' => $this->discount ? (Int) $this->discount : null,
];
return $arrayNull; // this is missing
}

Yii2 "has no relation named" at search model even the function it is defined in the main model

I am having a yii2 error : common\models\Book has no relation named "favorite".
When I try to add:
public function search($params) {
$query = Book::find();
$query->joinWith(['author', 'profile', 'favorite']);
In the book model I do have the public function:
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return '<i class="glyphicon glyphicon-asterisk books-form"></i>';
} else {
return '';
}
}
And also this extra function to get the icon
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return $icon;
} else {
return '';
}
}
And this works fine in the grid where I want to add sorting and filter:
[
'label' => 'Favorites',
'attribute' => 'favorite',
'value' => 'favoritedIcon',
'hAlign' => 'center',
'vAlign' => 'middle',
'format' => 'raw',
'width' => '50px',
],
I do some different things from another models I am using:
in the grid i get the value as an icon from the book model but i used this before.
the other thing is that the Favorite model has not the same name that the table but it work fine in the grid
abstract class Favorite extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'user_favorite';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['user_id', 'book_id'], 'required'],
[['user_id', 'book_id'], 'integer'],
[['selectedTime'], 'safe']
];
}
Any clues what I am doing wrong ?
======================================================
UPDATE after Pedro del Sol answer
There was some errors in the code but the main one was answered by Pedro, I do had a favorite function in the Book model but not favorites with multiple output.
So now it is working like that:
In the Book model
public function getFavorite() {
$userID = Yii::$app->user->identity->id;
return Favorite::find()->where(['user_id' => $userID, 'book_id' => $this->id])->one();
}
public function getFavorites() {
$userID = Yii::$app->user->identity->id;
return $this->hasMany(Favorite::className(), ['book_id' => 'id'], ['book_id' => $this->id]);
}
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return '<i class="glyphicon glyphicon-asterisk books-form"></i>';
} else {
return '';
}
}
In the BookSearch model:
public function search($params) {
$query = Book::find();
$query->joinWith(['favorites']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->setSort([
'attributes' => [
'title',
'author_id',
'rights_owner_id',
'user_favorite.user_id',
]
]);
and the grid view :
[
'label' => 'Favorites',
'attribute' => 'user_favorite.user_id',
'value' => 'favoritedIcon',
'hAlign' => 'center',
'vAlign' => 'middle',
'format' => 'raw',
'width' => '50px',
],
Having a method to getFavoritedIcon() is not the same as declaring a relation to getFavorite()
I assume that in your Book model class you have the methods getAuthor() and getProfile() which will return queries linking a Book with an Author and a Profile. You'll need something similar with Favorite(s) but I suspect the multiplicities will be different.
I think to declare your relation you'll need something like
/**
* #return \yii\db\ActiveQuery
*/
public function getFavorites()
{
return $this->hasMany(Favorite::className(), ['book_id' => 'ID']);
}
if the relation between Books and Favorites is one to many (most likely) or
/**
* #return \yii\db\ActiveQuery
*/
public function getFavorite()
{
return $this->hasOne(Favorite::className(), ['book_id' => 'ID']);
}
if the relation is one to one.
You can then use joinWith() with either 'favorite' or 'favorites' depending on the multiplicities of your relation.

Create new record using 2amigos SelectizeDropDownList in Yii2

I am trying to implement the 2amigos SelectizeDropDownList widget in a form to add new values to a table directly within the dropdown.
I am using the model Book and the Model Author so basically want to be able to add a new author in the book form.
This is the book controller at the update function:
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['index']);
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}
This is the form:
<?=
$form->field($model, 'author_id')->widget(SelectizeDropDownList::className(), [
// calls an action that returns a JSON object with matched
// tags
'loadUrl' => ['author/list'],
'value' => $authors,
'items' => \yii\helpers\ArrayHelper::map(\common\models\author::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
'options' => [
'class' => 'form-control',
'id' => 'id'
],
'clientOptions' => [
'valueField' => 'id',
'labelField' => 'name',
'searchField' => ['name'],
'autosearch' => ['on'],
'create' => true,
'maxItems' => 1,
],
])
?>
And this is the function author controller:
public function actionList($query) {
$models = Author::findAllByName($query);
$items = [];
foreach ($models as $model) {
$items[] = ['id' => $model->id, 'name' => $model->name];
}
Yii::$app->response->format = \Yii::$app->response->format = 'json';
return $items;
}
The form works fine to load, filter, search and add new items.
But it is not inserting the new typed attribute in the author table.
Do I need to add something in the book controller?
How can I check if it is a new value or a change of an existing author?
Thanks a lot
I made it work with the following code, not sure the most elegant because i am checking the if the author_id is a number or a string.
In my case the author won't be a number anyway.
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post())) {
$x = Yii::$app->request->post('Book');
$new_author = $x['author_id'];
if (!is_numeric($new_author)) {
$author = new Author();
$author->name = $new_author;
$author->save();
$model->author_id = $author->id;
}
if ($model->save()) {
return $this->redirect(['index']);
}
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}

Site and its behaviors do not have a method or closure named "orderBy"

while i am fetching this records then getting this error how to solve it ?
public function actionIndex()
{
$query = Site::model();
$pagination = new CPagination([
'defaultPageSize' => 5,
'totalCount' => $query->count(),
]);
$countries = $query->orderBy('name')
->offset($pagination->offset)
->limit($pagination->limit)
->all();
return $this->render('view', [
'countries' => $countries,
'pagination' => $pagination,
]);
}
you should distinguish between yii1 and yii2.from your codes,you used yii1 and yii2 together.

PHP, Yii2 GridView filtering on relational value

Following on from this:
Yii2 how does search() in SearchModel work?
I would like to be able to filter a GridView column of relational data. This is what I mean:
I have two tables, TableA and TableB. Both have corresponding models generated using Gii. TableA has a foreign key to a value in TableB, like this:
TableA
attrA1, attrA2, attrA3, TableB.attrB1
TableB
attrB1, attrB2, attrB3
attrA1 and attrB1 are the primary keys of their corresponding tables.
Now, I have a Yii2 GridView of attrA2, attrA3 and attrB2. I have a working filter on attrA2 and attrA3 so that I can search on column values. I also have a working sort for these two columns too - by just clicking on the column header. I would like to be able to add this filtering and sorting on attrB2 too.
My TableASearch model looks like this:
public function search($params){
$query = TableA::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$this->addCondition($query, 'attrA2');
$this->addCondition($query, 'attrA2', true);
$this->addCondition($query, 'attrA3');
$this->addCondition($query, 'attrA3', true);
return $dataProvider;
}
In my TableA model, I set the related value like this
public $relationalValue;
public function afterFind(){
$b = TableB::find(['attrB1' => $this->attrB1]);
$this->relationalValue = $b->relationalValue;
}
Although it is probably not the best way of doing this. I think I have to use $relationalValue somewhere in my search function but I'm not sure how. Similarly, I would like to be able to sort by this column too - just like I can for attrA2 and AttrA3 by clicking on the header link`. Any help would be appreciated. Thanks.
This is based on the description in the guide. The base code for the SearchModel comes from the Gii code generator. This is also assuming that $this->TableB has been setup using hasOne() or hasMany() relation. See this doc.
1. Setup search model
In TableASearch model add:
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), ['TableB.attrB1']);
}
public function rules()
{
return [
/* your other rules */
[['TableB.attrB1'], 'safe']
];
}
Then in TableASearch->search() add (before $this->load()):
$dataProvider->sort->attributes['TableB.attrB1'] = [
'asc' => ['TableB.attrB1' => SORT_ASC],
'desc' => ['TableB.attrB1' => SORT_DESC],
];
$query->joinWith(['TableB']);
Then the actual search of your data (below $this->load()):
$query->andFilterWhere([
'like',
'TableB.attrB1',
$this->getAttribute('TableB.attrB1')
]);
2. Configure GridView
Add to your view:
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
/* Other columns */
'TableB1.attrB1',
/* Other columns */
]
]);
Filtering a gridview by a column is damn easy in Yii 2.0. Please add the filter attribute to a gridview column having lookup values, as under:
[
"class" => yii\grid\DataColumn::className(),
"attribute" => "status_id",
'filter' => ArrayHelper::map(Status::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
"value" => function($model){
if ($rel = $model->getStatus()->one()) {
return yii\helpers\Html::a($rel->name,["crud/status/view", 'id' => $rel->id,],["data-pjax"=>0]);
} else {
return '';
}
},
"format" => "raw",
],
I'm stuck with this problem too, and my solution is rather different. I have two simple models:
Book:
class Book extends ActiveRecord
{
....
public static function tableName()
{
return 'books';
}
public function getAuthor()
{
return $this->hasOne(Author::className(), ['id' => 'author_id']);
}
And Author:
class Author extends ActiveRecord
{
public static function tableName()
{
return 'authors';
}
public function getBooks()
{
return $this->hasMany(Book::className(), ['author_id' => 'id']);
}
But my search logic is in different model. And i didn't find how can i implement search without creating additional field author_first_name. So this is my solution:
class BookSearch extends Model
{
public $id;
public $title;
public $author_first_name;
public function rules()
{
return [
[['id', 'author_id'], 'integer'],
[['title', 'author_first_name'], 'safe'],
];
}
public function search($params)
{
$query = Book::find()->joinWith(['author' => function($query) { $query->from(['author' => 'authors']);}]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => array('pageSize' => 50),
'sort'=>[
'attributes'=>[
'author_first_name'=>[
'asc' => ['author.first_name' => SORT_ASC],
'desc' => ['author.first_name' => SORT_DESC],
]
]
]
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
....
$query->andWhere(['like', 'author.first_name', $this->author_first_name]);
return $dataProvider;
}
}
This is for creating table alias: function($query) { $query->from(['author' => 'authors']);}
And GridView code is:
<?php echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'id',
'filter' => false,
],
[
'attribute' => 'title',
],
[
'attribute' => 'author_first_name',
'value' => function ($model) {
if ($model->author) {
$model->author->getFullName();
} else {
return '';
}
},
'filter' => true,
],
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
I will appreciate any critiques and advice.

Categories