Showing posts with label many-to-many. Show all posts
Showing posts with label many-to-many. Show all posts

Sunday, June 25, 2017

How to use the @ManyToMany with two lists on the same table

Leave a Comment

I have a situation where an entity could use another entity, and it could be used by another, so i have defined a ManyToMany relation that reference the same entity, so i could have listUse and listUsedBy, and both are persisted in the same table entity_usage :

@ManyToMany @JoinTable(name = "entity_usage",         joinColumns = {             @JoinColumn(name = "id_use", referencedColumnName = "id")},         inverseJoinColumns = {             @JoinColumn(name = "id_used_by", referencedColumnName = "id")}) private List<Entity> listUse;  @ManyToMany @JoinTable(name = "entity_usage",         joinColumns = {             @JoinColumn(name = "id_use_by", referencedColumnName = "id")},         inverseJoinColumns = {             @JoinColumn(name = "id_use", referencedColumnName = "id")}) private List<Entity> listUsedBy; 

Exemple : Entity A could use Entity B and C, so Entity B and C are used by A. Now my problem is when i add B and C to listUse, they are persisted in entity_usage, but when try to display listUsedBy i have to redeploy my project, otherwise listUsedBy remains empty, is there a way to refresh listUsedBy when persist my entity without having to redeploy my project.

1 Answers

Answers 1

This is the general approach:

@Entity public class SomeEntity {     @ManyToMany     @JoinTable(name = "entity_usage",         joinColumns = @JoinColumn(name = "using_id"),         inverseJoinColumns = @JoinColumn(name = "used_by_id"))     private Set<SomeEntity> using = new LinkedHashSet<>();      @ManyToMany(mappedBy = "using")     private Set<SomeEntity> usedBy = new LinkedHashSet<>();      public void addUsing(SomeEntity entity)     {         this.using.add(entity);         entity.usedBy.add(this);     }      public void addUsedBy(SomeEntity entity)     {         this.usedBy.add(entity);         entity.using.add(this);     } } 

and it's used:

public void someMethod(long parentEntityId, long childEntityId) {     EntityManager em = getSomeEntityManager();      SomeEntity parentEntity = em.find(SomeEntity.class, parentEntityId);     SomeEntity childEntity = em.find(SomeEntity.class, childEntityId);      parentEntity.addUsing(childEntity); } 

typically this is a transactional EJB method.
Note that there's no need to em.merge anything, since entities are already managed by em.find. Anyway, whichever method you'll use to manage your entities (query, find, persist, merge), remember that's important to call addUsing/addUsedBy only when both entities are managed.

This is one of the main incoherences that ORM logic cannot handle by its own: you have to inform both entities (parent and child) of their relation.
It's not sufficient to set the relation only on one side - if you only say that A is parent of B, B still doesn't know who is its parent.

However, there exists alternative approaches, like setting only the owning side of the relation (parent.getChildren().add(child)), flush, and refresh the child.

Nevertheless (as I experienced very well on my skin) the alternatives are very hard to handle in real world complex applications.

As a side note, I'd use Set instead of List for the relation, unless you need some kind of insertion-order.

Read More

Sunday, June 12, 2016

How to get many to many relationship items in laravel

Leave a Comment

There is a data structure for a e-shop:

Series -> (many to many) -> categories -> (many to many) -> products 

For example, series is "Outdoor Series" , categories is "t-shirt" , products are "t-shirt A, t-shirt B, etc... "

And here is the controller that list out products in one category

public function view($series = 0, $cat = 0, $page = 1) {         $category = Category::find($cat);          $totalItems = count($category->product);         $itemsPerPage = 30;         $currentPage = $page;         $urlPattern = "/ums/product/view/$series/$cat/(:num)";          $this->data['product_list'] = $category->product()->orderBy('created_at', 'desc')->skip(($page - 1) * $itemsPerPage)->take($itemsPerPage)->get();         $this->data['paginator'] = new Paginator($totalItems, $itemsPerPage, $currentPage, $urlPattern);         $this->data['category'] = $category;         $this->data['page'] = $page;         return view('product/list')->with($this->data);     } 

Now, the problem is , I would like to rewrite the code so that instead of showing one category, I would like to show one series as well.

That means if the $series = 0 , then it shows products in one category, if the $cat = 0, then it shows products in multi category

In laravel how to get the products in multi category? try $series->category->product() but no luck, also how to rewrite that function to support showing of the series?

Thanks a lot.

3 Answers

Answers 1

Assuming Laravel Model classes - Series, Category and Product

For the Series Model Class, create a function

   public function categories()    {         return $this->belongsToMany('App\Category');    } 

For the Category Model Class, create a function

   public function products()    {         return $this->belongsToMany('App\products');    } 

Now for a given Series, you can easily retrieve all related categories using the simple function call

$categories = $series->categories(); 

Finally coming to the main problem of showing products under multiple categories.

for($categories as $category) {      $productsOfThisCategory = $categories->products();      //save into some other data structure, say an array $allProducts } 

$allProducts will have multi-category products for a specific Series.

Refer : Standard eloquent relationship -Many to Many

Answers 2

You can use this answer for sorting.

How to sort by a field of the pivot table of a many-to-many relationship in Eloquent ORM

Answers 3

If I understand you correctly, then your models looks like below

class Series extends Model {      // other code      public function categories() {         return $this->belongsToMany('App\Category');     }      // other code }  class Category extends Model {      // other code      public function series() {         return $this->belongsToMany('App\Series');     }      public function products() {         return $this->belongsToMany('App\Product');     }      // other code }  class Product extends Model {      // other code      public function categories() {         return $this->belongsToMany('App\Category');     }      // other code } 

Further to get all products of certain series you need to do so

public function view($series = 0, $cat = 0, $page = 1) {     if (!empty($series)) {         $seria = Series::with(['categories' => function($query) {             $query->with('products');         })->find($series);         // or may be this will work, don't know         // Series::with('categories.products')->find($series);          // get all caegories from seria or certain one         if (empty($cat)) {             $categories = $seria->categories;          }         else {             $categories = $seria->categories()->where('id', $cat)->get;          }          // retrieve produts form each category and making from them collection         $products = $categories->map(function($category) {             return $category->products;         })->flatten();          // or use this approach if above not working         /*$products = collect([]);          foreach ($categories as $category) {             $produts = $products->merge($category->products);         }*/          // do your magic     }     else {         // not exactly understand what you want to do when $series is not set     }      // do your magic } 
Read More

Sunday, May 1, 2016

HasManyThrough with polymorphic and many-to-many relations

Leave a Comment

In my Laravel application I have the following classes:

class Product extends Model {     public function extended()     {         return $this->morphTo();     }      public function users {         return $this->belongsToMany('App\User', 'products_users', 'product_id');     } }  class Foo extends Model  {     public function product()     {         return $this->morphOne('App\Product', 'extended');     }     public function bars()     {         return $this->hasMany('App\Bar');     } }  class Bar extends Model  {     public function product()     {         return $this->morphOne('App\Product', 'extended');     }      public function foo()     {         return $this->belongsTo('App\Foo');     } }  class User extends Model {     public function products()     {         return $this->belongsToMany('App\Product', 'products_users', 'user_id');     } } 

I can easily get users of a bar object using Bar::find(1)->product->users and I can also get the bars of a user with User::find(1)->products.

How can I get the users of all bars belonging to a specific foo? That is, Foo::find(1)->users should return all users that have the bars belonging to Foo with id 1. It's basically hasManyThrough with polymorphic and many-to-many relations.

1 Answers

Answers 1

Try something like this:

public function users()     {         $Foo = static::with(['bars', 'bars.products', 'bars.product.users'])->get();         return collect(array_flatten(array_pluck($Foo, 'bars.*.product.users')));     } 

This should work for you (code has been tested, but not against the exact same structure as your setup). The first line will return all the users deeply nested through the relationships. The second line will pull out the users, flatten them into an array, then transform the array into a collection.

Read More