i have a model setup in Eloquent of a many to many relationship of Pages and Articles. My users have the ability to order these articles per page.
So my question is, can i order the relationship with sync...
$page->articles()->sync($specificOrderOfArticleIds)
And it sync the relationships in the order of that passed array.
so when i query the page
$page->articles() // in specified order
It's in the specified order.
Maybe i'm missing something obvious here?
In order to do that, you'll need some kind of column in the pivot table that defines an order. For example, an "order" column on the pivot table could be given a value using:
$page->articles()->sync([1 => ["order" => 1], 42 => ["order" => 6]]);
Then on the read, you can pass a closure into your query to specify the order be by this column.
$page = Page::whereId($id)->with(['articles' => function($query) {
$query->orderBy('pivot.order', 'asc');
}])->get();
The "pivot" keyword is used in relationship queries to point to the middle table.
However, you can use the closure ability shown above to order by anything you like, not just the pivot table. Depending on how you define the order, you may not even need the new pivot table column.
Related
I'm working on Laravel 5.8
Let's say we have a table products like this:
id | product_type_id |...
1 |______1_______|...
2 |______2_______|...
3 |______2_______|...
4 |______3_______|...
I would like to know how to get the all the products which "share" a product type.
In other words, I would like to get all the products except those whose product_type_id is unique in the table.
I know who to do it in a foreach loop but I would like to take advantage of the resources of using Laravel.
Thanks in advance.
The Laravel way of doing it would be using Eloquent relationships along with has() and whereHas(), like this:
$products = Product::whereHas('type', function ($builder) {
$builder->whereKey(Type::has('products', '>=', 2)->pluck('id'));
})->get();
I'm assuming you have defined Product and Type models and connected them together:
Product belongsTo Type
Type hasMany Product
If I'm understanding your question correctly you could do something like this.
First, make sure your ProductType model has a products relationship defined.
(I assume it does based on your Products table.)
Then you can query based on a relationship count using the Eloquent model method has.
Example:
ProductType::with('products')->has('products', '>', 1)->get();
The with('products') is optional. It simply grabs the Products at the same time to avoid additional queries. The whereHas method also works but is really only necessary if you need to filter the relationship based on more complex parameters.
You could also use the has/whereHas method inside the Product model using an inverse (ie belongsTo) relationship to get the same thing but inverted. It really depends on how you want the data presented to you.
Example:
Product::whereIn(
'product_type_id',
ProductType::has('products', '>', 1)->pluck('id')
)->get();
To sum it all up the first way will give you:
ProductType => Product
While the second example will give you:
Product => ProductType
See the related Laravel documentation for more info.
Hope that helps!
I have a author table and a publication table. Both are related to each other in a many to many relation. When I'm inserting the publications the authors of the publications are inserted in the pivot table by the order of authors id. But I need to insert it by the order i'm selecting the authors in the front-end. Whatever the order of the authors in the front end is it is getting ordered by the author's id in the pivot table. How can i stop this automatic ordering
You can't add rows in a specific order into a pivot table, because it doesn't really make sense.
Let's consider an users table:
The first user you enter will have the id 1
The second will be assigned to the id 2
And so on...
So you can enter the users in a specific order and retrieve them by their id.
However, in a standard pivot table, the primary key is composed by two columns, in your case the author_id and publication_id. Nothing new is created here, you just associate the primary key of two existing rows in two differents tables in order to achieve one - and unique - composed primary key.
If i explained well (and i hope so :p), you should understand why saying
But I need to insert it by the order i'm selecting the authors in the front-end.
doesn't really make sense.
But, don't worry, it is still possible to achieve your goal here.
Instead of using a pivot table, you can use a normal table with an id. This way, the order of insertion will be preserved. It will work but that's not very nice.
A better approach would be to add an additional column to the pivot table. A column like position. This column could be incremented for each author you insert. Then, you can order the table by the position column, by simply adding ->orderBy('position') to your relationship or every queries that needs to.
Here is an example to illustrate what i said above:
foreach($authors as $position => $author)
{
$publication->authors()->attach($author, ['position' => $position]);
}
If $authors contains the authors in the order you selected them on the front-end, they will be added accordingly.
If you need to sync instead of attach, that's also possible, it's just a little bit more verbose:
$syncData = $authors->mapWithKeys(function($author, $position){
return [$author->id => ['position' => $position]];
});
$publication->authors()->sync($syncData);
Don't forget that you can add false as a second parameter on the sync method so it'll only add new authors.
After that, just change your authors relationship in your Publication model like this:
public function authors(){
return $this->belongsToMany(Author::class)->orderBy('position');
}
Or everywhere you need to:
$publication->authors()->orderBy('position')->get();
I hope it helps !
So here's my problem.
I need to link an insurance policy to the insured property/item. Now the details vary greatly from car policy to a house or business one. So what I want to do is have something like this on the policies table
Policies
item_id
item_type
and that links to different tables depending on the value of the field "item_type" for example:
item_type = car then link to the cars table
item_type = house then link to the houses table
item_type = business then link to the businesses table
and so on...
I can do that on my own with php and mysql but I want to know the proper way to do it using CakePHP's table relationships and linking. I tried using the through option and a relationship table but it's not the same. Any ideas? or if a relationship table is the only way to do it then tell me how please.
This is actually a lot simpler than it first appears. I've done this a few times so I'll detail the technique that I use.
The first thing to do is create a behavior. This will allow you to enable any Table class in your application to have a policy attached to it.
The behavior is very simple. I've changed it to match your example, as I understand it. I'll talk through it after the code.
namespace App\Model\Behavior;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Query;
class PolicyBehavior extends Behavior
{
public function initialize(array $config)
{
parent::initialize($config);
$this->_table->hasMany('Policies', [
'className' => 'Policies',
'foreignKey' => 'table_foreign_key',
'bindingKey' => 'id',
'conditions' => ['table_class' => $this->_table->registryAlias()],
'propertyName' => 'policies'
]);
}
public function beforeFind(Event $event, Query $query, \ArrayObject $options, $primary)
{
$query->contain(['Policies']);
return $query;
}
}
So the in the initialize method we need to create a relationship to the table we attached the behaviour to. This will create a Table hasMany Policies relationship, meaning that any item in your system can have many policies. You can update this relationship to match how you're working.
You can see that there are a number of options defined in the relationship. These are important, as they link the tables items together. So the table_foreign_key is a field in your policies db table used to store the primaryKey of the related item. So if you're attaching a Policy to a Car, this would be the Car.id. The bindingKey is the key used in the Policy table to join on.
In order to filter the different types of attachments, you need the table_class field in your policies db table. This will be the name of the attached table class. So Cars, Cats, Houses etc. Then we can use this in the conditions, so anything pulling the primary table class will automatically filter the related Policies to match.
I've also configured the propertyName, this means that any item you look for which contains Policies will have an entity property called policies with the related data inside.
The last function in the behaviour is the beforeFind, this just ensures that whenever you look for the primary table class, you always return the related policies, you don't have to use this if you don't want to, but I found it handy to always have the related data in my use-case.
So then, how do we use this new behaviour? Just attach it like you would any other behaviour, and that's it. $this->addBehavior('Policy').
Be aware
This just reads data, you'll need to ensure that you save the table alias, and the foreignKey into the related table when creating new items.
Just for clarity, your policies table schema will need, at a minimum.
policies.id
policies.table_class VARCHAR(255)
policies.table_foreign_key INT(11)
I have three database tables in my application. As an example, here is what I have now:
items TABLE: id, quantity, price, month_id (FK), department_id (FK)
months TABLE: id, month
department TABLE: id, department
I have already defined hasMany and belongsTo relations on the models. With these relations, I'm able to perform the query App\Month::first()->items to retrieve all first month items. However, if I want to query on both month and department, what is the best way to do it in this case?
I can certainly do it like this:
$month = App\Month::first();
$month->items()->where('department_id', '3')->get();
Is there more elegant way of doing this query?
First of all you can tell Eloquent to eagerly load needed relations to get better performance. You can do this by using with() method and defiling all needed relations - see http://laravel.com/docs/5.0/eloquent#eager-loading for more details.
Moreover, if you want to apply some additional criteria on loaded relations, you can do so using the same with method, but using different syntax to define relations and criteria:
$users = User::with(['posts' => function($query)
{
$query->where('title', 'like', '%first%');
}])->get();
I have two models in cakePHP, one is Parts and the other is Orders.
Parts is a simple model with an id column plusaction multiple fields such as customer, part number, description etc. Each part has its own unique id.
Orders is another simple model with a field called *part_id* which represents an id from the Parts model.
In an action, in the Orders controller I wll need to pull all the orders for that particualr view, find information about that order from the Parts database and put it together in an array ready to be passed to the view.
For example that tables might look like:
ORDERS
Part_id id
2 5
1 7
PARTS
customer partnumber description id
Google XHA-V1 widget_1 1
Yahoo YVS-XN widget_5 2
Since I will have 50-100 orders in any one view, I need an efficient way to reference each order's *part_id* in the Parts database and combine this into one array. Its the searching of the Parts database multiple times that I'm unsure how to achieve effectively.
Note: I guess the solution could be cake or pure PHP.
If you have correctly defined the relation in your Order Model, you should be able to retreive all the corresponding Parts
$this->Orders->find('all');
cakephp will do it in two queries:
- a SELECT for all the Orders
- a SELECT for all the Products where product.id in ( list_of_Orders_ids )
and then it will contruct an array in a proper format
If you need an special query, you could also "manually" set the joins for the query, but you'll need to set the recursive to -1:
$this->Orders->find('all',array('recursive'=>-1,
'fields'=>array('Order.*','Part.*'),
'joins'=>array(array('table' => 'parts',
'alias' => 'Part',
'type' => 'INNER', //or left
'conditions' => array('Part.id = Order.part_id')))))
Good Luck!
You can get an array of (all) Orders using the $this->Order->find('all', array('recursive' => 2)); in your controller. Each order also contains the related Parts information.
Imo customer information should be part of the Order, not of Part. Also you might want Orders to consist of multiple Parts.