Cphalcon: Many to Many behaviour

Created on 1 Oct 2014  路  30Comments  路  Source: phalcon/cphalcon

The way many to many behaves, does not appear to be very intuitive, and is not nice to use. for example

$tag1 = \Tag::findFirst('id=1');
$tag2 = \Tag::findFirst('id=2');
$tag3 = \Tag::findFirst('id=3');

// create a post
$post = new \Post();
$post->tags = array($tag1, $tag2);
$post->save();

// edit a post
$post = \Post::findFirst('id=1'); // the same post that was created earlier
$post->tags = array($tag1, $tag3);
$post->save();

Will tend to mean the post ends up with all three tags, possibly even with an extra copy of tag1, rather than just 2 as might be expected, and how Doctrine works.

Is this a bug, or intended behaviour?

Some official word on this would be great, seems to attract a few people asking about it http://forum.phalconphp.com/discussion/2190/many-to-many-expected-behaviour

thanks :)

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

5.0 new feature request transfer

Most helpful comment

This is still relevant.

All 30 comments

Bump...

Bump :+1:

Bump :)

Not a bug, but probably something that can be improved with an array_unique

@phalcon - are you meaning an array_unique in the c code? Because if you mean in our php, I am a little unclear where it would go, as we are assigning an array with unique elements

Talk about other frameworks here may be not too polite, but... Can't Phalcon have a simple sync method like Laravel has?

You may also use the sync method to attach related models. The sync method accepts an array of IDs to place on the pivot table. After this operation is complete, only the IDs in the array will be on the intermediate table for the model:

$user->roles()->sync(array(1, 2, 3));

Refer to http://laravel.com/docs/4.2/eloquent#inserting-related-models.

I mean, sure, but its really not necessary if things worked correctly?

You could simply retreive your user model, and array push "role" entries into that "users" "roles" array. Or you could set the users roles array to an empty array (essentially removing all roles from the user), and array push roles 1 2 and 3 as with your example. Invoke save on the user and you are done..

Refer to my post here:
http://forum.phalconphp.com/discussion/2190/many-to-many-expected-behaviour#C12840

Am I the only one seeing how broken this all is? There seems to be so much confusion around this. None of this works as expected lol.

@prodigga No, you're not. I agree with you :-)

My comment is just a potential next step, because there's no need to load your associated entities from database before attach them to your entity. I mean, in real life you don't SELECT id FROM table WHERE id = 1. All you need are references, like Doctrine and Eloquent do.

@lfbittencourt very good point! @phalcon just tagging phalcon to see if we can get a response, this issue is buried somewhere in the closed list

@phalcon said:

Not a bug, but probably something that can be improved with an array_unique

Seems like a bug to me.

@Pajamaman +1

Have someone come up with a solution to this issue? It will do with plain PHP-code...

This small issue steered me away from Phalcon a year ago.

Well i will look into it when i have time, but to be honest i created service myself to create/update many to many relation records.

@Jurigag could you please share that code with me? I consider doing it myself, but if you have already done it, I would be happy to use it!

I created the following method that I placed in my base model: http://pastebin.com/MikKbSFj

Wouldn't it be easier to just remove all the relations between the current post and tags and then to add them again this is how I normally deal with this kind of problem when it comes to many-to-many relations. Even when u look from the UI side its always either show all the related tags already added with an option to add another one. So either u use ajax and add only the one or two new tags or submit all the tags with a normal request again and use the method which I described above.

Wouldn't it be easier to just remove all the relations between the current post and tags and then to add them again

The original post was more intended to find out of Phalcon is working as intended. Seems the official line is that it is, so no bug...?

Unsure if I have been spoilt by Doctrine doing more for me than phalcon, or fallen into some kind of anti-pattern / over-reliance on the ORM!

I created the following method that I placed in my base model: http://pastebin.com/MikKbSFj

It works fine :+1: but it would be even better if you could do like laravel 5

$article->sync('categories', [1, 2 => ['hidden' => 1], 3])

@firstactivemedia sorry I was just referring to the resolution which @phalcon provided I wasn't talking about if its a bug or not :) But when u talk about object oriented approach than yeah I totally agree about the fact that if you assign an array to a object property it should be overriten like it would be in normal object.

This is still relevant.

Still doesn't work.

Lol. So, is phalcon dead?

Not at all. We just don't have a lot of resources to fix everything. Some of the issues have been opened a long time ago and we have a bot that closes them automatically. For a good number of these issues they are either forgotten, fixed or not pursued. The rest of them we reopen them manually since they are indeed issues we need to address.

This still not implemented?

Some input please. Using the original example

$tag1 = \Tag::findFirst('id=1');
$tag2 = \Tag::findFirst('id=2');
$tag3 = \Tag::findFirst('id=3');

// create a post
$post = new \Post();
$post->tags = [$tag1, $tag2];
$post->save();
// Post is related to tag1 and tag2

// edit a post
$post = \Post::findFirst('id=1'); // the same post that was created earlier
$post->tags = [$tag1, $tag3];
$post->save();
// Post is related to tag1 and tag3

This is the intended behavior. If we were to take your approach and the edit action would append records, how would you be able to delete a relationship using edit? For instance based on your suggestion, the proposed functionality will give us:

// edit a post
$post = \Post::findFirst('id=1'); // the same post that was created earlier
$post->tags = [$tag1, $tag3];
$post->save();
// Post is related to tag1, tag2 and tag3

So the question is, how would you delete say tag1 from the relationship and "replace" it by say tag4 ? If we do not use the direct assignment of what you need to save, then we would have to make an extra call to first delete what we do not want and then assign what we do.

Thoughts on this?

@prodigga @ghost @lfbittencourt @Pajamaman @fl0pp @Jurigag @makerlabs @angelvega93 @StudioMaX @firstactivemedia

Closing in favor of #13855. Will revisit if the community votes for it, or in later versions.

Bump

This is what I am planning to do, the keepMissingRelationship toggle is bothering me. I already have a similar approach working in Phalcon 3.4. I am currently upgrading it to phalcon 4.

Let's say

  • handling lots of relationships
  • recursively saving nested entities indefinately
  • belongsTo, hasOne, hasMany, hasManyToMany should behave the same
  • matching using multi-field support for relationship binding + forced find condition
  • handling model->assign (phalcon 4) or model->save (phalcon 3)
  • allowing data manipulation for hasManyToMany nodes as well
  • allowing whitelist and getColumns to be passed recursively for aliases as well
  • Soft-delete friendlyness on relationships nodes
  • Avoiding deleting or re-creating nodes, they could contain some persistant data as well (i.e position)
  • Allowing many-to-many node data manipulation
  • Allowing to pass a flag if you want to delete missing relationship or keep them

Saving?

  • beging transaction
  • save current model (we have to do it here because the primary keys could possibly be changed)
  • get the relationships properties and values using the aliases

    • find their primary keys (can we have combined keys in phalcon?)



      • if primary key is not empty, fetch that entry





        • if soft-delete behaviour, force soft-delete field to the softDelete->notDeleted value





      • if entry can't be found, create new entry


      • assign updated data and nested relationships to that entry



    • if "keepMissingRelationships" flag is false



      • delete missing entries



    • save and automagically save nested relationships recursively)

    • forward children messages to parent model



      • use alias to prefix fields



  • if error, rollback, commit otherwise

Assigning

        $tagList = [];
    $tagList []= true; // keep missing relationship flag could be passed here if we wanted to have different bahaviour at different nested levels
    $tagList []= Tag::findById(1); // append or replace
    $tagList []= ['name' => 'Tag 2']; // create and append
    $tagList []= ['name' => 'Tag 3', 'PostNode' => ['position' => 3, 'Post' => ['name' => 'Post Name'], 'Tag' => ['name' => 'Tag 3!']]]; // create and append and then update node position
    $tagList []= Tag::findById(4)->assign(['PostNode' => ['position' => 4]]);

    $post->TagList = $tagList; // first way

    $whiteList = ['TagList' => ['name' => true, 'PostNode' => ['position' => true]]];
    $post->assign(['TagList' => $tagList], $whiteList); // second way, using nested whitelist

    $keepMissingRelationships = true;
    $model->save($keepMissingRelationships); // actually do shits using begin rollback & commits, also forward sub getMessages() to their parent model up to the main one
Was this page helpful?
0 / 5 - 0 ratings