Skip to content

afterFind() and beforeFind() callbacks not working on associated models [was: Translate Behavior should translate associated model data] #1730

Closed
lh-import opened this Issue Oct 11, 2013 · 51 comments

6 participants

@lh-import

Created by Predominant, 6th Mar 2013. (originally Lighthouse ticket #95):


(Reported by: thinline)

What Happened

Using the Blog example if you have Posts which belogsTo Categories and Category actsAs Translate. Within the Posts controller calling $this->Post->find() or read() does not translate the Category relationship. This happens if the Category model is loaded.
See https://trac.cakephp.org/ticket/3680

I am only filing this here because I can't not find a search feature in the new code.cakephp.org and would like to make sure this gets into the to-do-list

@lh-import

10th Dec 2009, Predominant said:


(Comment by: sebastian)

Hi! About one year ago I've posted something similar on Trac (don't have the time to find the link). In the meantime I've used a workaround as poLK suggested me on IRC, many thanks to him (http://groups.google.com/group/cake-php/browse_thread/thread/9b7e60900269643b). Hope this enhancement will be added to 1.3 core very soon, because it's very useful when developing multilanguage apps.

Changes:
version:1.3-dev

@lh-import

10th Dec 2009, Predominant said:


(Comment by: ceeram)

the problem that causes this is explained by this ticket:
https://trac.cakephp.org/ticket/2056

cake doesnt support behaviour callbacks of a related model.

Changes:

@lh-import

10th Dec 2009, Predominant said:


(Comment by: ionas)

Changes:
version:1.3.0-alpha
type:bug
title:afterFind() and beforeFind() callbacks not working on associated models [was: Translate Behavior should translate associated model data]
tags:Bug 2056
https://trac.cakephp.org/ticket/2056
Callbacks on associations

@lh-import

10th Dec 2009, Predominant said:


(Comment by: mark_story)

Not a bug, its an enhancement

Changes:
type:enhancement

@lh-import

11th Dec 2009, dakota said:


here is a old patch that might be a good starting point (Thanks to ethooz for finding it :) )

@lh-import

11th Dec 2009, cake_joe said:


This one is more recent:
https://trac.cakephp.org/attachment/ticket/2056/dbo_source.2.php (If predominant/markstory still has access to the old trac's data)
In google cache:
http://209.85.135.132/search?q=cache:_4WOAl0mQRgJ:https://trac.cakephp.org/attachment/ticket/2056/dbo_source.2.php

As far as I remember some core behaviors have to be converted too, probably that is one reason why there is no change to this for more than 2 years.

@lh-import

11th Dec 2009, Andrew Ashbacher said:


I made a patch that's similar to the one linked by Ionas. However, since ModelBehavior itself has an afterFind method, I added a check for:

!empty($associatedBehavior->forceAfterFind)

This way this way the behavior's afterFind method will only be called if it is explicitly instructed to do so by adding the following to the behavior:

var $forceAfterFind = true;

Maybe, in order to facilitate the addition of other callbacks, this could be extended to:

var $forcedCallbacks = array('afterFind');

Then the addition of $forcedCallbacks = array() to ModelBehavior and a check of in_array() instead of !empty().

Patch sttached.

Cheers,
enthooz

@lh-import

11th Dec 2009, cake_joe said:


The issue not only appears with behaviors, but in general. If you have Post HasMany Comment, Comment BelongsTo Post and Post has recursive > -1 or you use contains (or complex find conditions, or linkable) and do $this->Post->find(), Comment::beforeFind() and Comment:afterFind() will not get called, as far as I remember. I had huge issues on implementing model domain based permissions that filteres associated data automagically using beforeFind (e.g. it did not work like expected).

As far as I get your patch by looking at it, it is about
a.) afterFind only, not beforeFind
b.) it is about the callback(s) of attached behaviors only, not the model itself

(Note: what about Model::save and Model::saveAll, do callbacks on associations and associated behaviors work perfectly there?)

@lh-import

11th Dec 2009, Andrew Ashbacher said:


I apologize, I was thinking locally, acting locally. That patch made possible what I needed at the time but definitely needs to be further developed. I shared it merely as a starting point and/or for informational purposes.

Do you, by chance, have a set of test cases for this issue?

@lh-import

5th Jan 2010, dakota said:


Any more progress made on this issue?

@lh-import

26th Feb 2010, WebTechNick said:


I too would like to see this implemented. Behavior callbacks (beforeSave()) aren't being executed on associative saveAll() calls. Really a shame.

@lh-import

27th Feb 2010, Mark Story said:


Instead of just saying 'I would like this feature' why not implement it in a patch, and include some test cases? Not like you lack the source code, or the skills to modify PHP code :)

@lh-import

1st Mar 2010, WebTechNick said:


Hehe, indeed. I'll give it a go. =)

@lh-import

10th Mar 2010, Jamie said:


Just curious - why is this an enhancement and not a defect? I'm talking about the main model callbacks (beforeFind, afterFind, etc.) not working on associated models. I can't think of a logical reason for why associated model callbacks would be ignored. Also, unless I'm missing it, nothing in the book hints that these callbacks will be ignored for associated models. So, I went over to the book and submitted an edit for review. Hopefully it gets in. ;)

@lh-import

10th Mar 2010, SISTEMIO.com said:


totally agree Jamie

@lh-import

10th Mar 2010, cake_joe said:


As far as I know the reasons are:

  • The current way it works has been like this for more than 2 years (since early 1.2 at least)
  • It breaks some core behaviors (at least did in 1.2)
  • Speed issues may arise
  • It is probably not trivial to implement
  • A contributor who wants takes care of this and at the same time is able to implement it has not yet been found (I, for one, feel not able to do so)
@lh-import

10th Mar 2010, Predominant said:


Current design of the model structure and callbacks systems prevent callbacks being called in a recursive manner like is proposed.

The reason this ticket is marked as an enhancement is because the ticket suggests an alteration / enhancement over the current implementation.

This is one that could quite easily get out of hand, especially with circular dependencies / associations.

@lh-import

10th Mar 2010, Jamie said:


Fair enough. Hopefully my edit to the Book is approved soon then, to clear up any confusion others might have about this issue.

@lh-import

9th Apr 2010, Jpsy said:


Whow!!!

After a lot of struggling I found and read this ticket. And I can't help but feeling very frustrated.

After working with CakePHP for some weeks now I was very enthusiastic about the framework. Now I stumbled into the cracks of i18n and found that this section of Cake is just not production ready. I can't think of many multi-language applications that would not use associations and thus lure a developer into this trap. And (at least with my level of understanding) it's close to impossible to solve without touching the core.

PLEASE be so kind and at least approve Jamie's edit. It would have saved me - and will probably save many other developers - a lot of pointless debugging and searching. Early knowledge of the existing limitations would allow for the planning of alternative i18n strategies already in the design phase. Right now developers only discover the show stopper when they are deep into enemy territory.

For the interested and desperate reader: Far from a universal solution I found at least some relieve here (jitka's posting).

@lh-import

6th Oct 2010, MoAX said:


So, what about this issue for a further version of Cake ? Will we wait until CakePHP 2.0 gets out of the bakery ?
I have also no efficient workaround to patch this problem in CakePHP 1.3.4, anyone got a solution please ?

@lh-import

8th Oct 2010, Mark Story said:


#1178 was a duplicate of this.

@lh-import

27th Feb 2011, Koterpillar said:


Here's a patch I maintain since the old Trac ticket, for 1.3.7.

@lh-import

28th Feb 2011, Koterpillar said:


Sorry, fixed patch.

@lh-import

1st Mar 2011, Tomas Maly said:


Even with Koterpillar's recent patch, any modifications to $query inside beforeFind() do not integrate with the query (such as in hasMany). It seems that in trying to hack it, generateAssociationQuery() simply ignores $query in a hasMany or HABTM relationship. I would hope there would be some sort of way to affect the association - for now, I have to edit $assocData on the fly elsewhere.

@lh-import

1st Mar 2011, Koterpillar said:


Tomas, can you show an example of what's not working for you? I have several other behaviors which implement beforeFind, and it is called where needed.

@lh-import

6th Aug 2011, Kim Biesbjerg said:


Koterpillar: I applied the patch to CakePHP 1.3.11 and beforeFind/afterFind callbacks is indeed now called. The problem is that the query isn't modified by TranslateBehavior which means associated records isn't translated.

        Configure::write('Config.language', 'eng');
        $results = $this->Book->find('first', array('recursive' => 1));
        pr($results);

Produces the following SQL:

SELECT `Book`.*, `Category`.*, `I18n__name`.`content` FROM `books` AS `Book` LEFT JOIN `i18n` AS `I18n__name` ON (`Book`.`id` = `I18n__name`.`foreign_key` AND `I18n__name`.`model` = 'Book' AND `I18n__name`.`field` = 'name') LEFT JOIN `categories` AS `Category` ON (`Book`.`category_id` = `Category`.`id`) WHERE `I18n__name`.`locale` = 'eng' LIMIT 1

As you can see Category model is not i18n for some reason.

Do you have this working on your install?

@lh-import

2nd Sep 2011, watermark86 said:


I'm running vanilla CakePHP 1.3.10. For associated models, the afterFind callback functions fine if it's directly on the model, but will not if it's in a behavior. I don't see how this is an enhancement.

@lh-import

16th Jan 2012, Doug said:


In CakePHP 1.3.11 I was using Taylor's neat Attribute Behaviour:
http://bakery.cakephp.org/articles/taylor.luk/2008/11/04/attributebehavior-dry-and-powerful

For related models Taylor wrote a workaround which is based on using the model's afterFind() to fire the behaviour's afterFind().

This was working so I assumed that related model's behaviour's afterFind() are not triggered but related model's callbacks were.

I've brought it over to CakePHP 2.0 but now the Model's afterFind() isn't triggered so the workaround doesn't work.

I need to do more research but I've put this note here for now.
thanks.

@lh-import

27th Feb 2012, sibidiba said:


Just would like to confirm this bug persists on CakePHP 2.X as well.

If you write callbacks in models, they are executed - as expected - even if being part of just an associated model.

However, contrary to what one would except, the very same callbacks are not executed if they are part of a behavior.
Behavior callbacks only execute on the primary model, not on any of the associated ones.

This severely limits the usefulness of behaviors. In particular I do not see a real life use case for TranslateBehavior if it won't be translated if associated.

@lh-import

9th Mar 2012, Andy Hobbs said:


Hi,

Just to clarify the way 2.x works with related model callbacks and find operations:

Where:

'Primary' has a 'Secondary'

and both have a behavior 'Testable' which has callbacks defined to log a simple message and also the models have callbacks to log a simple message

Calling:

$this->set('primary', $this->Primary->find('first', 
    array(
       'conditions' => array('id' => $id), 
       'contain' => array('Secondary')
    )));

or the same without the contain restriction

You get the following:

All callbacks for Primary are called and only the Model afterFind for Secondary is called

Output from debug log:

2012-03-09 10:18:46 Debug: BEHAVIOR beforeFind in Primary
2012-03-09 10:18:46 Debug: MODEL beforeFind in Primary
2012-03-09 10:18:46 Debug: MODEL afterFind in Secondary
2012-03-09 10:18:46 Debug: BEHAVIOR afterFind in Primary
2012-03-09 10:18:46 Debug: MODEL afterFind in Primary

I think that it would be relatively straight forward to change DboSource.php to call model and behavior callbacks at the time of retrieving the associated records. The two main problems with this that I see though are

  1. is that even the correct place in the process to execute the callbacks as the initial model find query would already have been executed.
  2. What impact would this have on existing behaviors that are not expected to be called multiple time for one find query. E.g. ContainableBehavior will try and alter the associations each time a call back is called. Perhaps either extra callbacks could be used like beforeInitialFind() or during the behavior set up the behavior could define at which point the callbacks should be fired.

It also seems to me that the associated Model beforeFind not being called is perhaps a bug and not just a feature of the current callback implementation?

This is currently a big issue for me as everywhere I am getting an associated model that should be translated I am including extra parameters in my contain statements to ensure the correct language is picked out. I am happy to do any work required for refactoring/implementing changes if the appropriate people can produce/agree on a spec for how it should work

Many Thanks

@lh-import

9th Mar 2012, sibidiba said:


I believe the main issue at hand is the very inconsistency you pointed out.

Callbacks must behave exactly the same way, whether defined on a Model or a Behavior level.

  • I assume all callbacks of Models should be triggered for the primary Model and then for each associated one.
    afterFind() has explicitly a $primary parameter to be able to distinguish between being a primary or an associated Model.
    beforeFind() does not have such a parameter. But why shouldn't it behave the very same way?

  • Behavior callbacks must work the very same way as in the Models. Otherwise combining callbacks and associations would not make sense using Behaviors, which is at the moment a severe limitation of Behaviors. TranslateBehavior simply does not make any sense at the moment. I believe this is unintended, therefore a bug.

Using - and adding - the $primary parameter all changes can be made backward compatible I believe. I.e. Containable behavior can check this parameter to behave the same way.

@lh-import

9th Mar 2012, ADmad said:


Remedying the fact that behavior afterFinds are not called for associated model is relatively easy. This is where the model's afterFind for associated model's is triggered so there you just have to add a statement to trigger it's behavior's afterFinds too.

Dealing with beforFinds is complicated though (as pointed out beforeFinds don't have $primary param but we can add that, that's not a big problem). The issue comes when dealing with models associated by hasOne and belongsTo. Fields for such models are retrieved by doing joins not querying those model separately. So you really can't modify the $queryData in beforeFind of these associated model's, it will most definitely end up in a disaster. So to even start fixing this problem for beforeFind you would have to stop doing joins which would be quite inefficient.

Edit: So there is no quickfix to this problem. In any case the current ORM is long overdue an overhaul which would be done in the next major release 3.0. Suggestions for the same are welcome.

@lh-import

9th Mar 2012, sibidiba said:


I'm really glad someone is picking this up.

Changing how beforeFind() works sounds really another issue, because it does work consistently at the moment: for associated models it is triggered neither in the Model nor an attached Behavior.
(However, it could be implemented regardless of the joins. In that case it could get the primary $queryData passed. It could now this for the $primary parameter having a special value.)

@lh-import

14th Apr 2012, merovinq said:


Is there a patch for this... I have a cakephp 1.3 application that I am migrating over to 2.1 and when I call $this->Primary->find('all', array('contain'=>array('Secondary'))); The Secondary afterFind callback does not trigger in cake 2.1 like it did in 1.3...

I need to get this work asap

@lh-import

16th Apr 2012, Julien Buratto said:


There are a couple of duplicate tickets on this topic and many requests too, so it means that the "expected behaviour" is that callbacks are executed also on associated models, or at least that could be an optional param.

I would be glad to help coding (and that's what I've tried to do based on posts on this thread) but could not get it to work.

@ADmad: you gave us a nice hint to get into the right line, could you also tell us wich lines to write in ? :-)

@lh-import

16th Apr 2012, ADmad said:


@Julien Here's a diff against 2.2 branch. Use at your own risk, haven't actually checked to ensure it does what it's supposed to :)

@lh-import

20th Apr 2012, TeckniX said:


Just to add to this enhancements, I believe associated models should also load behaviors for beforeSave, afterSave, etc.
Currently it doesn't seem to do so?

@lh-import

23rd Apr 2012, Julien Buratto said:


@ADmad: thanks for the hint. I've tested the patch but got some warnings. Maybe because I'm running 2.1. I will upgrade to git version and let you know :-)

@lh-import

23rd Apr 2012, Julien Buratto said:


@ADmad: tested also on Git version. I think your code pass an instance of an object of different type coz I get:
Argument 1 passed to TranslateBehavior::afterFind() must be an instance of Model, instance of Mysql given [CORE\Cake\Model\Behavior\TranslateBehavior.php, line 266

However it's a good point to start with, thank you.
Anyone can give hints ? I'm available to do the typing and testing work if someone patch the patch :-)

@lh-import

24th Apr 2012, ADmad said:


@Julien Read the error message carefully, check the stack trace, think over it and you should be able to fix it yourself :)

@lh-import

25th Apr 2012, Julien Buratto said:


ADmad: afterFind method of the linked model is now triggered (attached diff).
Unfortunately that does not translate the linked model. I supposed that is caused by the fact that behaviors are more complex than this :-(

@lh-import

25th Apr 2012, Julien Buratto said:


In case some other are looking for a dirty way to have the Translate behaviour to work a little, apply the previous patch and then open your AppModel.php file and add:

public function afterFind($a,$t=false){
    if ($t===false AND isset($this->actsAs['Translate'])){
        $this->L10n = new L10n();
        $this->L10n->get(Configure::read('Config.language'));
        $found=$this->query("SELECT content FROM i18n WHERE ".
                "model='".$this->name."'".
                " AND foreign_key=".$a[0][$this->name]['id'].
                " AND field='".$this->actsAs['Translate'][0]."' ".
                " AND locale='".$this->L10n->locale."'"
        );
        $a[0][$this->name][$this->actsAs['Translate'][0]]=$found[0]['i18n']['content'];
    }
    return $a;
}

This will fetch the traslated field of a model that has actAs set.

@lh-import

9th May 2012, euromark said:


I struggled with the very same issue in 1.3 (as I would in any other version, I assume).

Got a $this->User->find('first', array('contain'=>array('Planning'))) - Planning has a SoftDelete Behavior attached.
but since the callbacks for beforeFind etc dont get triggered the behavior doesnt work as it is supposed to.

I agree that beforeFind and belongsTo/hasMany could get nasty.
But we not supporting the more trivial cases, then? In my case it would be a hasMany relation.
Also - if the default value for "associationCallbacks" was false, it wouldn't hurt to support this even if it could break under some circumstances (which should be documented the, of course).
Using at at his own risk, one could then use sth like:

find(..., array('associationCallbacks'=>true, ...))

The thing is: the whole behavior concept is unusable at some places without them working across relations...

@lh-import

1st Jan 2013, Julien Buratto said:


Still wondering why this was not addressed in 2.x
Everytime I have to multilanguage a site I fall on this gap...

@lorenzo
CakePHP member
lorenzo commented Oct 14, 2013

This needs to finally work in 3.0, changing milestone

@lorenzo
CakePHP member
lorenzo commented Jan 5, 2014

This is working in 3.0 already

@lorenzo lorenzo closed this Jan 5, 2014
@dereuromark
CakePHP member

:clap: One of the most important issues regarding behaviors/callbacks, YEAH

@rchavik rchavik referenced this issue in croogo/croogo Jan 25, 2014
Closed

Taxonomy is not translated in blocks #269

@trenshaw
trenshaw commented Dec 2, 2014

I realise this has been addressed in 3.0 but I think this enhancement really ought to be included in the next 2.x release.

I have written a number of behaviors that should work on associated models. Model associations are core to the framework. I feel a behavior applied in AppModel should apply to all models, associated or not.

This is fundamental functionality, I don't think we should all been tasked with upgrading our apps to 3.0 for something to work as expected (since 1.3!)

@josegonzalez
CakePHP member

@trenshaw if you have a pull request - or some good ideas! - implementing this for 2.x, it would be much appreciated. The 2.x ORM isn't amenable to having a good solution for this - one that won't involve lots of difficult recursion and raise more issues than it's worth. As well, triggering related model callbacks would be a BC break, so I can imagine lots of people would complain about that.

@jayapaliwal28 jayapaliwal28 added a commit to jayapaliwal28/GtwPosts that referenced this issue Jan 9, 2015
@jayapaliwal28 jayapaliwal28 Update condition of Posts.status= publish into Paginator setting as c…
…akephp 2.x does not allow to load behaviour of associated model

see: cakephp/cakephp#1730
8fafdb8
@chicagosky

Here is my input to this peculiar problem

I have a find condition that has 'contains' multiple other associated models. I have set callbacks to false and all associated models are retrieved except for 1. I am unable to figure out why? If callbacks is set to true then there is no problem.

I am using CakePHP 2.5 Any suggestions?

@jiru jiru added a commit to Tatoeba/tatoeba2 that referenced this issue Feb 29, 2016
@jiru jiru Fix on the fly gen. of transcrip. for translations
This fixes on the fly generation of transcriptions of translations
(i.e. not retrieved from the database).

Now we’re using Containable to get translations, the afterFind()
method of the Transcriptable behavior doesn’t get called for
translations. So let’s call it manually instead.

This is a CakePHP limitation. Seems to be solved in 3.0.
cakephp/cakephp#1730
8e02388
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.