How to parse an array within a query builder's get() - laravel

I have my Country model and a State Model. Only countries that have states can show states, or return [];
I would like to return this in one hit into my response object, yet I can't wrap my head around getting the id of the current country that is running in the query, I built this just now:
return response()->responseObject([
'code' => 200,
'status' => true,
'message' => 'Retrieved List of Available Countries',
'data' => Country::where('allow_registration', Country::REGISTRATION_ALLOWED)->get([
'id',
'name',
'iso',
'iso3',
'prefix',
'states' => function($query){}
])
]);
And I have another function waiting for this:
public static function getStates($id)
{
$states = State::where('country_id', $id);
return $states ? $states : [];
}
Am I approaching this right? What do I have to put in my sub query function in order for current country ID? Or did I approach this wrong and there is a more eloquent way of implementing?

in your country model create states relation
$this->hasMany(State::class);
then you can get all countries with its states by that :
Country::with('states')->get();

Related

Laravel - Get array with relationship

I have an ajax call that returns an array:
$reports = Report::where('submission_id', $submissionID)
->where('status', 'pending')
->get(['description','rule']);
return [
'message' => 'Success.',
'reports' => $reports,
];
From this array, I only want to return the fields 'description' and 'rule'. However I also want to return the owner() relationship from the Report model. How could I do this? Do I have to load the relationship and do some kind of array push, or is there a more elegant solution?
You can use with() to eager load related model
$reports = Report::with('owner')
->where('submission_id', $submissionID)
->where('status', 'pending')
->get(['id','description','rule']);
Note you need to include id in get() from report model to map (owner) related model
you will have probably one to many relationship with Reports and owners table like below
Report Model
public function owner() {
return $this->belongsTo('App\Owner');
}
Owner Model
public function reports() {
return $this->hasMany('App\Report');
}
your controller code
$reports = Report::with('owner')->
where('submission_id', $submissionID)->where('status', 'pending')->get()
return [
'message' => 'Success.',
'reports' => $reports,
];
This is what I ended up going with:
$reports = Report::
with(['owner' => function($q)
{
$q->select('username', 'id');
}])
->where('submission_id', $submissionID)
->where('status', 'pending')
->select('description', 'rule','created_by')
->get();
The other answers were right, I needed to load in the ID of the user. But I had to use a function for it to work.

How to change result of laravel relationship

I have an one to one relationship in laravel same as following:
public function Category()
{
return $this->belongsTo(Categories::class);
}
and run this eloquent query:
Product::with('category')->first();
this query return:
{
"name": "Green Book",
"category": {
"id": 1,
"name": "Book"
}
}
but I went this data:
{
"name": "Green Book",
"category": "Book"
}
Is it possible to do this without using a loop?
First of all it seems like you have a 1-X relationship which you are mistakenly using as a many to one. Your belongsTo should be hasOne since the your items have one category not the other way around.
You can use the $appends property to append a custom field and make it behave as though it's part of your model:
Rename your relationship and add a mutator and accessor:
public function categoryRelationship()
{
return $this->hasOne(Categories::class);
}
public function getCategoryAttribute() {
return $this->categoryRelationship->name;
}
public function setCategoryAttribute($value) {
$this->categoryRelationship->name = $value;
}
You can also choose to add an event to automatically save your relationship when the model is being saved to ensure it works transparently:
protected static function booted()
{
static::saving(function ($myModel) {
$myModel->categoryRelationship->save();
});
}
}
Finally you add the $appends property to ensure your new attribute is alwasy included in the model as though it's a native one.
protected $appends = [ 'category' ];
// This is so you don't end up also showing the relationship
protected $hidden = [ 'categoryRelationship' ];
You can use leftJoin
return \App\Product::leftJoin('categories', 'products.category_id', '=', 'categories.id')
->select('products.*', 'categories.title as category')->get();
According to Laravel Doc Eager Loading Specific Columns
You may use the following
Product::with('category:id,name')->first();
Or you may do it yourself :
$product = Product::with('category')->first();
$product->category = $product->category->name;
I get same result
so, i tried but didn't get right result.
how to change the result of laravel eloquent relation.
I found one way and used transformer.
we can change the result in transformer.
$result = Event::with(['eventType'])
->where('id', $id)
->where('user_id', auth()->user()->id)
->first();
return $this->trans->transform($result);
this is the result and i used transformer as follow.
public function transform(Event $Event)
{
return [
'id' => $Event->id,
'company_id' => $Event->company_id,
'calendar_id' => $Event->calendar_id,
'case_id' => $Event->case_id,
'user_id' => $Event->user_id,
'title' => $Event->title,
'description' => $Event->description,
'duration' => $Event->duration,
'alert_at' => $Event->alert_at,
'alert_email' => $Event->alert_email,
'email_sent' => $Event->email_sent,
'alert_popup' => $Event->alert_popup,
'popup_triggered' => $Event->popup_triggered,
'created_by' => $Event->created_by,
'completed' => $Event->completed,
'alert_offset' => $Event->alert_offset,
'icon' => $Event->icon,
'color' => $Event->color,
'snoozed' => $Event->snoozed,
'type_id' => $Event->type_id,
'datetime' => $Event->at,
'endtime' => $Event->end_time,
'event_type_name'=>$Event->eventType->name,
];
}
then, we can get right result. Focuse on 'event_type_name'=>$Event->eventType->name,
But i have one problem yet.
now, the result is only 1 row. it is just first().
but if i use get(), i can't use transformer.
of course, i can loop the result using foreach().
but i think it is not right way.
what is better way?
Please answer.

Algolia search (Scout) with multiples fields

Let' say I want to create a phone book for a big company with multiples offices.
My database is composed by 3 tables:
- User
- City (represent the office's location)
- Role (a User can have multiple roles)
I'd like to create a form with 3 different fields to perform the query, how can I do that ?
For now I've got only 1 field and this is my main model User
```
public function city()
{
return $this->belongsTo(City::class);
}
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function toSearchableArray()
{
$office = $this->city;
$array = [
'name' => $this->name,
'phone' => $this->phone,
'postal_code' => $office->postal_code,
'city' => $office->name,
];
return $array;
}
```
How would you do a research with Algolia and add filters to the query (City and Roles) ?
How should I store Roles ? Plain text ?
Thank you
Scout only support numbers in the where clause: https://laravel.com/docs/5.5/scout#where-clauses
I would recommend to also store the ID for offices and do search like:
User::search('')->where('office_id', 2)->get();
For Roles, because you have multiple values, you should store them as in an array.
You cannot use where on arrays, so you will need to leverage the callback parameter to add a filter entry in the options array.
User::search('', function ($algolia, $query, $options) {
$options = array_merge($options, [
'filters' => 'roles:engineer'
]);
return $algolia->search($query, $options);
})->get();
You can find Algolia's doc about filtering here: https://www.algolia.com/doc/guides/searching/filtering/
Please let me know if that worked for you.

Yii2: Join condition parameters are null when using "with"

I have a many to many relation:
Parent(id) <- Link(parent,child,sort) -> Product(sku)
I want to get a sorted list of Products from the Parent model
On the parent model I have a couple of functions:
public function getLinksDown()
{
return $this->hasMany( DBLink::className(), ['parent' => 'id'])
->from(['linksDown' => DBLink::tableName()]);
}
public function getProducts()
{
return $this->hasMany(DBProduct::className(), ['sku' => 'child'])
->from(['products' => DBProduct::tableName()])
->via('linksDown')
->innerJoin(
['linx' => DBLink::tableName()],
'linx.child = products.sku AND linx.parent=:parent',
[':parent' => $this->id]
)
->orderBy(['linx.order' => SORT_ASC]);
}
If I call the relation directly it works fine.
$model = Parent::findOne(1234);
$products = $model->products;
Products is a list of products sorted correctly.
If the relation is called as a part of a "with" it fails, $this->id will be null which means no products are returned.
Parent::find()->with('products')->all();
After much head scratching, I have decided the only solution, is to abandon the Many to Many relation where I need a sorted list of products, and do something like this:
$model = Parent::find()->where(['id' => 1234])->with('linksDown.products')->one();
and reference each product through the linksDown relation, instead of directly from the parent model.
foreach( $model->linksDown as $link ) {
dosomethingwith($link->product);
}
In order to make this work the sort will need to be applied to the linksDown relation:
public function getLinksDown()
{
return $this->hasMany( DBLink::className(), ['parent' => 'id'])
->from(['linksDown' => DBLink::tableName()])
->orderBy([linksDown.order => SORT_ASC]);
}

ActiveRecord where and order on via-table

I have three database table:
product (id, name)
product_has_adv (product,advantage,sort,important)
advantage (id, text)
In ProductModel I defined this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id']);
}
I get the advantages without any problems.
But now I need to add a where product_has_advantage.important = 1 clausel and also sort the advantages by the sort-columen in the product_has_advantage-table.
How and where I have to realize it?
Using via and viaTable methods with relations will cause two separate queries.
You can specify callable in third parameter like this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'], function ($query) {
/* #var $query \yii\db\ActiveQuery */
$query->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
});
}
The filter by important will be applied, but the sort won't since it happens in first query. As a result the order of ids in IN statement will be changed.
Depending on your database logic maybe it's better to move important and sort columns to advantage table.
Then just add condition and sort to the existing method chain:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}
Using viaTable methods with relations will cause two separate queries, but if you don't need link() method you can use innerJoin in the following way to sort by product_has_advantage table:
public function getAdvantages()
{
$query = AdvantageModel::find();
$query->multiple = true;
$query->innerJoin('product_has_advantage','product_has_advantage.advantage = advantage.id');
$query->andWhere(['product_has_advantage.product' => $this->id, 'product_has_advantage.important' => 1]);
$query->orderBy(['product_has_advantage.sort' => SORT_DESC]);
return $query;
}
Note than $query->multiple = true allows you to use this method as Yii2 hasMany relation.
Just for reference https://github.com/yiisoft/yii2/issues/10174
It's near impossible to ORDER BY viaTable() columns.
For Yii 2.0.7 it returns set of ID's from viaTable() query,
and final/top query IN() clause ignores the order.
For who comes here after a while and don't like above solutions, I got it working by joining back to the via table after the filter via table.
Example for above code:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->innerJoin('product_has_advantage','XXX')
->orderBy('product_has_advantage.YYY'=> SORT_ASC);
}
Take care about changing XXX with the right join path and YYY with the right sort column.
First you need to create a model named ProductHasAdv for junction table (product_has_adv) using CRUD.
Then create relation in product model and sort it:
public function getAdvRels()
{
return $this->hasMany(ProductHasAdv::className(), ['product' => 'id'])->
orderBy(['sort' => SORT_ASC]);;
}
Then create second relationship like this:
public function getAdvantages()
{
$adv_ids = [];
foreach ($this->advRels as $adv_rel)
$adv_ids[] = $adv_rel->advantage;
return $this->hasMany(Advantage::className(), ['id' => 'advantage'])->viaTable('product_has_adv', ['product' => 'id'])->orderBy([new Expression('FIELD (id, ' . implode(',', $adv_ids) . ')')]);
}
This will sort final result using order by FIELD technique.
Don't forget to add:
use yii\db\Expression;
line to head.
I`ve managed this some how... but it needs additional work after.
The point is that you have to query many-to-many relation first from source model and after that inside that closure you should query your target model.
$query = Product::find();
$query->joinWith([
'product_has_adv' => function ($query)
{
$query->alias('pha');
$query->orderBy('pha.sort ASC');
$query->joinWith(['advantage ' => function ($query){
$query->select([
'a.id',
'a.text',
]);
$query->alias('a');
}]);
},
]);
Then you just have to prettify the sorted result to your needs.
The result for each row would look like
"product_has_adv": [
{
"product": "875",
"advantage": "true",
"sort": "0",
"important": "1",
"advantage ": {
"id": "875",
"text": "Some text..",
}
},
As explained by #arogachev, the viaTable uses two separate queries, which renders any intermediate orderBy obsolete
You could replace the viaTable with an innerJoin as follows, in a similar solution to #MartinM
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::class, ['pha.product' => 'id'])
->innerJoin('product_has_advantage pha', 'pha.advantage = advantage.id')
->andWhere(['pha.important' => 1])
->orderBy(['pha.sort' => SORT_ASC]);
}
By adjusting the result of hasMany, you are adjusting the query for the target class - AdvantageModel::find(); product_has_advantage can be joined via the advantage identity
The second parameter of hasMany, link, can be viewed as [ query.column => $this->attribute ], which you can now support via the joined product_has_advantage and its product identity
Note, when using viaTable, the link parameter can be viewed as if the intermediate query is complete and we are starting from there; [ query.column => viaTable.column ]
hence ['id', 'advantage'] in your question
public function getAdvantages()
{
return $this
->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}

Resources