Skip to content

Loading…

DDC-181: Order of many-to-many relationship #2466

Closed
doctrinebot opened this Issue · 10 comments

2 participants

@doctrinebot

Jira issue originally created by user amirabiri:

I've come across the need for this in a scenario which is analogous to orders / products.

I've got an "order" which is linked to one or more "products". However the order (position) in which the products appear in the order is significant. Therefore I have a "position" field in the join table.

The requirement is:

  • To map the order of the array to values of the "position" field in the join table upon save.
  • To hydrate the array ordered by the position field when hydrating the object.
@doctrinebot

Comment created by romanb:

I think is a more general improvement for persisting the order / the keys of any collection.

This is not trivial though. I dont think it can make it into 2.0 but I will schedule it for 2.1.

@doctrinebot

Comment created by amirabiri:

How about a temporary solution? At the moment to work around this limitation I need to keep two collections in my object:

# A collection of the join table records, which I am forced to raise to an entity status. This collection persists.
# A collection of the actual entities on the other end of the many-to-many relationship.

Perhaps a possible temporary solution is to create a collection that basically does that but in an encapsulated way? If I had such a collection all I would really need is to re-order it with a map and a closure on @PostLoad. That would still mean that Doctrine saves me a hell of a lot.

I think properties of a join link are a very common need. I came across this limitation more than once with 1.x as well. Even with Propel before that.

@doctrinebot

Comment created by romanb:

Your current solution is not bad at all if I understand it right. It is normal that when you have additional fields in the join table you need to map the join table as an association class. Call it a "many-to-many between A and B through C". Its how you would do it in plain OOP, too.

Given:

A manytomany B

Now you want to add a field to the association. You can not add it to class A or B. You need to create a class in-between (C, the association class).

That results in:

A onetomany C
C manytoone A
B onetomany C
C manytoone B

I agree, however, that the order is something special, since it is implicitly represented by the order of the elements in the collection. You dont need an association class in plain OOP for that and Doctrine should be able to handle that transparently, too. I agree there, its just not yet possible.

Right now the only way to order a collection-valued association is in a DQL query or in PHP, I guess you know that already.

Additionally, we want to add something like: @OrderBy("foo ASC, bar DESC") that you can apply on a collection-valued association so that the collection is always ordered, whether you use DQL or find()/findAll()/lazy loading/ ...

However, this does not yet cover the "persistence" of the order in the relational database which is the more complicated part.

I dont understand your proposed temporary solution. Can you show some (peudo)code that shows how you imagine that?

Thanks!

@doctrinebot

Comment created by amirabiri:

I am not disputing what you say, I'm just saying that Doctrine can offer a collection class that encapsulates the repeating pattern of join properties.

Consider the following code snippet:
(Assume that each article has a different priority in each category it is in, where the priority can be only "Low", "Medium" or "High")

/****
 * @Entity
 * @Table(name="article")
 */
class Article
{
  // ...
}

/****
 * @Entity
 * @Table(name="article_category")
 */
class ArticleCategory
{
    const PRI_LOW  = 1;
    const PRI_MED  = 2;
    const PRI_HIGH = 3;

    /****
     * @OneToMany(targetEntity="Article",...)
     */
    private $article;

    /****
     * @OneToMany(targetEntity="Category",...)
     */
    private $category;

    /****
     * @Column(type="integer")
     */
    private $priority;
}

/****
 * @Entity
 * @Table(name="category")
 */
class Category
{
    /****
     * @ManyToMany(targetEntity="Article",...,joinEntity="ArticleCategory")
     */
    private $articles;



    public function getArticles()
    {
        return $this->articles->toArray(); // Returns an array of articles.
    }

    public function getArticleByIndex($idx)
    {
        return $this->articles[$idx];
    }

    public function getArticlePriority($idx)
    {
        return $this->articles->getJoinEntity($idx)->getPriority();
        // or alternatively:
        return $this->articles->joinEntities[$idx]->getPriority();
    }
}

Since Doctrine 2.0 is already being a bit invasive with the PersistentCollection class anyway, this does not present any additional invasiveness. You just get a subclass of PersistentCollection called PersistentManyToManyCollection if you specify the joinEntity in the @ManyToMany tag.

If you provide this functionality, the users can take care of such things like the order by themselves. They can also do it in a less invasive manner: they have full control over how to store the order property.

@doctrinebot

Comment created by romanb:

Thats not how you map a many-to-many with an association class. There is no @ManyToMany at all.

See below:

class Article {
   private $id;
   /*** @OneToMany(targetEntity="ArticleCategorization", ....) **/
    private $categorizations;
}

class Category {
   private $id;
   /*** @OneToMany(targetEntity="ArticleCategorization", ...) **/
   private $categorizations;
}

class ArticleCategorization {
    private $id;
   /****
    * @ManyToOne(targetEntity="Article", ...)
    * @JoinColumn(...) 
    */
    private $article;
   /**** 
    * @ManyToOne(targetEntity="Category", ...)
    * @JoinColumn(...)
    */
    private $category;
   /****
    * @Column(type="integer")
    */
    private $priority;
}

Now, order is a different thing. Just to persist the order of a collection should not require an association class (currently it does), I guess we agree on that.

I dont see how PersistentCollection is invasive, at least its the best we can do. You program to the Collection interface, you never need to care about PersistentCollection and should certainly not use methods that are not on the Collection interface. Collections of entities can not be arrays. PHP's arrays are not suitable as collections of entities. (We're still waiting for something like SplArray that implements sth like an SplCollection interface, the OO version of arrays ;).

@doctrinebot

Comment created by amirabiri:

My point was that instead of breaking it down like that, it can be defined as a @ManyToMany with a twist.

I wasn't criticizing you about PersistentCollection, I was merely repeating what you guys wrote in the manual. I completely agree with your design decision there and I also blame PHP for not implementing the whole Array / Object fiasco properly. All I'm saying is that what I'm proposing would not introduce any new constraints to the entities.

@doctrinebot

Comment created by @guilhermeblanco:

Both expose same enhancement.

@doctrinebot

Comment created by @beberlei:

This is a duplicate of DDC-213

@doctrinebot

Issue was closed with resolution "Duplicate"

@beberlei beberlei was assigned by doctrinebot
@doctrinebot doctrinebot closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.