Optimize query in Laravel Backpack n-n relationships - php

I am building a backend panel for a website with Laravel Backpack. It is really nice, but I have noticed that relationship queries are very expensive.
I have two models: Product and Center with a many to many relationship between them. In my CenterCrudController I have defined a field this way:
$this->crud->addColumns([
// More fields...
[
'label' => 'Products',
'type' => 'select2_multiple',
'name' => 'products', // the method that defines the relationship in your Model
'entity' => 'products', // the method that defines the relationship in your Model
'attribute' => 'name', // foreign key attribute that is shown to user
'model' => 'App\Models\Product', // foreign key model
'pivot' => true, // on create&update, do you need to add/delete pivot table entries?
],
// More fields...
]);
It works fine, showing a select multiple field with related models. But the query used is SELECT * FROM products, which is highly expensive (table products have thousands of records with about 25 columns).
In this example I only need id and name fields. I am looking for something like Query Builder select() method.
Is there a way for optimizing this type of query?
Thanks in advance!

Not sure if this is actually an answer, but I'll post it anyway.
The best solution (as pointed by #tabacitu) was using select2_from_ajax field. It doesn't slow page load and make an ajax request for retrieving data only when user clicks on the select field.

Related

Laravel-Backpack Getting data of 2 tables by join

I'm new using backpack for laravel and I'm trying to understand how could show data from DB in the default view that backpack use to display rows. I already read the documentation from the site but it's really poor, and have a lot of questions.
I have 2 models linked by join, for example:
Table 1 Table 2
-id -id
-name -phone
-age -description
-table2_id
How can I display the attributes from table 2 in table 1 list view?. Backpack haves this view to list elements of modules
I want to see on that table the combination of the 2 tables...Any code that could help me ?. Thank you for your help.
Backpack creates CRUD Panels for you Eloquent Models. Not your database tables. So in order to have columns showing up that show elements from another table, you need to properly define the relationships between those Models. Then you can use the select column to show that connected entry.
[
'name' => 'rm_id',
'label' => 'Relationship Manager',
'type' => 'select2',
'model'=>config('permission.models.role'),
'options' => (function ($query) {
return $query->where('roles.name', 'Relationship Manager')
->join('model_has_roles', 'model_has_roles.role_id','=','roles.id')
->join('users', 'model_has_roles.model_id','=','users.id')
->select('users.id','users.name')->get();
}),
'attribute'=>'name',
],

How to create a database relationship between 3 entities (items, their categories and additional fields)?

I'm trying to create a database (using MySQL) for a simple Yii-based website. It will contain a catalog with goods, which are divided into a few categories. The idea is to have a way to add additional fields, specific for each category, and these fields should be fillable for each good in a category.
For example, if I have a category 'wallpapers', they should have additional fields 'color', 'footage', 'facture' etc. An of course these fields must be filled differently for each wallpaper item.
I created a database with 4 tables for it: items (which contains items, obviously), items_categories (with categories for therm), items_fields (contains additional fields) and items_fields_values (a support table for many-to-many relationship which also contains a values of related fields).
My database schema diagram (you need to have at least 10 reputation to attach images).
I'm using Yii Framework, and relationships in models described in a such way:
Item model:
return array(
'category' => array(self::BELONGS_TO, 'ItemCategory', 'category_id'),
'fields' => array(self::MANY_MANY, 'ItemField', 'items_fields_values(item_id, field_id)'),
);
ItemField model:
return array(
'itemCategory' => array(self::BELONGS_TO, 'ItemCategory', 'item_category_id'),
'items' => array(self::MANY_MANY, 'Item', 'items_fields_values(field_id, item_id)'),
);
ItemCategory model:
return array(
'items' => array(self::HAS_MANY, 'Item', 'category_id'),
'itemsFields' => array(self::HAS_MANY, 'ItemField', 'item_category_id'),
);
How should I define these relationships to have a way to output, add and edit fields to category and their values to specific items?
What you are describing is an EAV data model (entity-attribute-value).
An extension might solve your problem for accessing the data. Take a look at https://github.com/yiiext/eav-behavior

How to access sub-objects of Yii relational query

A table Product contains has_many relationship with a table **Slab** that further contains has_many relationship with a table Rate.
A relational query is wrapped inside CActiveDataProvider that joins three tables across certain parameters and return products in descending order of their respective rates.
I want to show the results in tabular form through CGridView.
Trying to access only certain columns through following syntax:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns'=>array(
'name','slabs.id','slabs.rates.rate'
)
));
Unfortunately I can't access slabs.id because when I dump dataProvider object I see that it is annexed to Product object through an Array whose index[0] contains the Slab object, then under Slab object index[0] has the rates.rate object.
It is probably occurring because of the has_many relationship between tables but my query would always return one Slab and one Rate object.
How may I access and show them?
I guess there are two possible workarounds.
1. Change or add a relationship so you can access a slab and a rate with a has_one relationship.
2. You can add a column to the gridview with a custom name and value like this:
'columns'=>array(
'name',
array(
'name' => 'Slab Id',
'value' => '$data->slabs[0]->id',
),
array(
'name' => 'Rate',
'value' => '$data->slabs[0]->rates[0]->rate',
)
)
Where value is a string representing an expression to be evaluated. $data is the main model, in this case Product.

cakephp find query conditions on associated model

I have a model User and a model Role in a CakePHP application. The association between the two models is the following:
User $belongsTo Role
Role $hasMany User
I want to make a query on the User model to find all users with a specific role (let's say the role Supervisor). I did my query like this:
$supervisors = $this->User->find('all', array(
'contain' => array(
'Role' => array(
'conditions' => array(
'Role.name' => 'Supervisor'
)
)
)
));
But the above query returns me all the users in my users table. It does not return only the users with role Supervisor. I know that if I do two queries, one on the Role model to find the id of the role type 'Supervisor' and then do another query on the User model and pass the id of the supervisor role record in the conditions on my User model like this:
$supervisor_role_id = $this->Role->field('id', array('Role.name' => 'Supervisor'));
$supervisors = $this->User->find('all', array(
'conditions' => array(
'User.role_id' => $supervisor_role_id
)
));
The above queries will give me the desired result. But I don't wanna do 2 queries to do this. Why doesn't the first approach work. Any idea please?
Thank you
The reason your attempt didn't work
CakePHP's Containble Behavior creates separate queries for each model. So - what you did was basically described like this: "Find all Users. Also find any Roles with the name of 'Supervisor". As you can see, there is no condition that crosses between the two.
So, you can do one of the following:
1) [easy way] Query the other way around
Query from the Role model and contain it's user(s). This pulls the role you want (based on your provided conditions) then contains any/all of it's users.
Note - if you've already loaded the 'User' model (or it's been loaded by default because you're in the UsersController), you can run your find like this: $this->User->Role->find(..... - so you don't have to load the Role model separately.
2) Use JOINs (see CakePHP Book on Joining Tables)
This allows you to limit the result of a parent model based on it's associated data.

Save a HABTM record to database CakePHP

I am working on a followers / following system with CakePHP 2.
I have setup my database with a users table, and a user_users table. The users table is the main table containing every user on the system, whilst the user_users table contains the records of followers.
I then have a UsersController, User model and Follower model.
I can successful output a button to either say Follow or Following dependent on whether the currently logged in user is following the user of which the profile they are viewing belongs to, however what I am unable to understand how to do, is create new following relationships in the table. In other words, I do not know how to create records in the user_users table.
I am not sure where the logic for this should go, and thus what my "Follow" button should point to.
This is probably a very simple question, but I am totally stumped. I have tried adding a "follow" action to the UsersController but I cannot get that to work.
Any help much appreciated,
Duncan
HATBM isn't a good fit in this situation. From the cookbook:
HABTM data is treated like a complete set, each time a new data
association is added the complete set of associated rows in database
is dropped and created again so you will always need to pass the whole
data set for saving. For an alternative to using HABTM see hasMany
through (The Join Model)
For this reason, HABTM is mainly good for pretty 'dumb' relationships. I've used it in cases such as where a User has to select many Interests - and they just get a list of checkboxes, where they can click multiple Interests, and save them all in one hit.
In your case, it'll be easier to have a separate table with it's own model. I'd call it Relationships or something similar. It would have an id, followed_by_id, following_id, and any other fields you may need.
I've dug up some code from an old cake 1.3 app, but it should help you out. Your Relationships model would look something like this:
<?php
class Relationship extends AppModel {
var $name = 'Relationship';
var $belongsTo = array(
'FollowedBy' => array(
'className' => 'User',
'foreignKey' => 'followed_by_id'
),
'Following' => array(
'className' => 'User',
'foreignKey' => 'following_id'
)
);
}
?>
Your User's model would have to have relationships like this:
var $hasMany = array(
'Followers' => array(
'className' => 'Relationship',
'foreignKey' => 'following_id',
'dependent'=> true
),
'FollowingUsers' => array(
'className' => 'Relationship',
'foreignKey' => 'followed_by_id',
'dependent'=> true
),
);
Then in your relationships controller, you'd have methods something like this:
function add($following_id = null) {
$this->Relationship->create();
$this->Relationship->set('followed_by_id',$this->Auth->User('id'));
$this->Relationship->set('following_id',$following_id);
$this->Relationship->save();
$this->redirect($this->referer());
}
function delete($id = null) {
$this->Relationship->delete($id);
$this->redirect($this->referer());
}
Note that in that code, I'm modifying the database with a GET request - which I really shouldn't be doing (it's old code, from years ago). You'll want to enforce a POST request for both the add and delete methods, since they're modifying the database.
But still, that code should set you on the right track.

Categories