SitePoint Sponsor

User Tag List

Page 5 of 5 FirstFirst 12345
Results 101 to 118 of 118

Thread: ActiveRecords

  1. #101
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okey, I worked on this some more, and got the whole column-meta business out of the way. Back to philosophizing about associations..

    There is an interesting problem with eager loaded has many associations when combined with a limit. Since you only want the limit to affect your main objects, not the associated ones, you basically have to make one extra query, or at least that's how I solved it.

    The first query selects all unique IDs with SELECT DISTINCT and applies the limit. It then supplies this list of IDs to the main query, which runs a condition that looks something like WHERE id IN (1, 2, 3, 4...) where the numbers are the list of IDs from the first query.

    This brings out another matter. Eager loaded associations can't be limited. This of course only applies to one-to-many associations. I tried to think of a way around it but hasn't come up with anything so far. Maybe it doesn't matter. If you need limiting capabilities for a one-to-many, perhaps you are better off lazy loading it.

    Speaking of normal loading, or lazy loading which I think is the correct term (I hope). 33degrees wrote something similar to this a while back:
    PHP Code:
    $user = new User();
    $user findByPk(1);
    echo 
    $user->group;
    echo 
    $user->getProfile()->email
    Would it be an idea perhaps to dynamically create the getProfile() method with the __call() function? Which would basically check if a) association is already loaded, so return it, or b) association not loaded, run a finder (optionally using any supplied parameters such as limit/order) and return the association.

    You could of course override this method with a custom one in your model.

    My current implementation of "lazy" loading is quite horrible, I just reused the standard syntax so I'd do:
    PHP Code:
    $user->profile->email
    Which of course makes it impossible to supply any form of conditions.

    Just some food for thought.. As always, interested in hearing how others handle these things.

  2. #102
    SitePoint Guru
    Join Date
    Nov 2003
    Location
    Huntsville AL
    Posts
    701
    Mentioned
    4 Post(s)
    Tagged
    1 Thread(s)
    Might want to take a look at section 8.14 of the PHP Doctrine manual: http://www.phpdoctrine.net/doctrine/...query-language

    It talks about some of the techniques used to apply LIMIT against records and not rows.

  3. #103
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Interesting read, but yeah, they're doing almost exactly what I'm doing. Running another query inside the main one, to select distinct IDs, and then do a WHERE id IN() in the main query. They do not seem to support limits on the joined rows, so I suspect there is no good way to do that.

  4. #104
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    From my understanding, what rails does when you retrieve a get many association is return a proxy that you can either iterate over, or use as a finder. So you'd do something like:

    PHP Code:
    $user->profile->email->find/* something */); 
    to add further criteria/ordering/limiting.

  5. #105
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    From my understanding, what rails does when you retrieve a get many association is return a proxy that you can either iterate over, or use as a finder. So you'd do something like:

    PHP Code:
    $user->profile->email->find/* something */); 
    to add further criteria/ordering/limiting.
    Alright, but that brings up the question of what the default value would be, if you don't use a finder. Say you'd do:
    PHP Code:
    foreach ($blog->comment as $comment
    And you didn't do any eager loading. How many comments would be fetched? How would they be ordered? Or is that just not possible?

    Problem for me when doing
    PHP Code:
    $user->profile->find/* options */); 
    is that whatever is found is not stored in $user->profile. If a finder could, instead of returning objects, do something like:
    PHP Code:
    return $this $objects
    I guess it would work, but I don't think that's possible? (Overwriting $this inside the class)

    This is what originally led me to the conclusion that the getter must not only instantiate a new object, it must also run a finder. And since you probably need to be able to supply it with values, it would be better to do getter methods for each association rather than relying on __get().

  6. #106
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    Alright, but that brings up the question of what the default value would be, if you don't use a finder. Say you'd do:
    PHP Code:
    foreach ($blog->comment as $comment
    And you didn't do any eager loading. How many comments would be fetched? How would they be ordered? Or is that just not possible?
    The finder way is for overriding the default behavior, which you define along with the association itself. What I do now is:

    PHP Code:
    // set up the association
    $this->has_many('comments', array('order'=>'posted_at DESC'));

    // retrieve default
    $blog->comments;

    // override default
    $blog->find('comments', array('limit'=>10)); 
    Which I don't consider perfect (I prefer the rails way), but works for now

    Quote Originally Posted by pakmannen View Post
    Problem for me when doing
    PHP Code:
    $user->profile->find/* options */); 
    is that whatever is found is not stored in $user->profile. If a finder could, instead of returning objects, do something like:
    PHP Code:
    return $this $objects
    I guess it would work, but I don't think that's possible? (Overwriting $this inside the class)
    You can't overwrite $this inside a class, no. But in this case I don't see why it's a problem for the finder to return an object?

    Quote Originally Posted by pakmannen View Post
    This is what originally led me to the conclusion that the getter must not only instantiate a new object, it must also run a finder. And since you probably need to be able to supply it with values, it would be better to do getter methods for each association rather than relying on __get().
    Yes in the case of lazy loading, it makes sense for the getter to run a finder, but in the end doing $blog->getComments(array('limit'=>'10')) results in the same thing as $blog->comments->find(array('limit'=>'10')), it comes down to which syntax you prefer. The latter has the advantage of being able to do things like $blog->comments->add($comment), which would for example set the foreign key of the passed in comment object and save it, and may be harder to express using the getter syntax.

  7. #107
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    PHP Code:
    // set up the association
    $this->has_many('comments', array('order'=>'posted_at DESC')); 
    Ah, I see. That's a nice way of managing defaults.

    You can't overwrite $this inside a class, no. But in this case I don't see why it's a problem for the finder to return an object?
    Okay, consider this:
    PHP Code:
    $user = new User();
    $user $user->findByPk(1);

    echo 
    $user->id;
    echo 
    $user->group;
    echo 
    $user->profile->findcondition );
    echo 
    $user->profile->email// wouldn't use the object found above
    echo $user->profile->website// neither would this..

    // I'd have to do:
    $user->profile $user->profile->findcondition )
    echo 
    $user->profile->email// etc 
    Yes in the case of lazy loading, it makes sense for the getter to run a finder, but in the end doing $blog->getComments(array('limit'=>'10')) results in the same thing as $blog->comments->find(array('limit'=>'10')), it comes down to which syntax you prefer. The latter has the advantage of being able to do things like $blog->comments->add($comment), which would for example set the foreign key of the passed in comment object and save it, and may be harder to express using the getter syntax.
    All right, I see where you're going with the add() method but I still don't see how $blog->comments would store whatever it retrieves with the finder in itself by just typing: $blog->comments->find(array('limit'=>'10'))?

  8. #108
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post

    Okay, consider this:
    PHP Code:
    $user = new User();
    $user $user->findByPk(1);

    echo 
    $user->id;
    echo 
    $user->group;
    echo 
    $user->profile->findcondition );
    echo 
    $user->profile->email// wouldn't use the object found above
    echo $user->profile->website// neither would this..

    // I'd have to do:
    $user->profile $user->profile->findcondition )
    echo 
    $user->profile->email// etc 
    But that's a has one relationship you're describing, so why would you need conditions? The finder syntax is only really useful for a has many relationship, in which case you're dealing with a result object or array anyway.

  9. #109
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    But that's a has one relationship you're describing, so why would you need conditions? The finder syntax is only really useful for a has many relationship, in which case you're dealing with a result object or array anyway.
    True, I just took that example since it was used previously. Hmm, so, say we need to update something in certain comments, how would you write that?
    PHP Code:
    foreach ($blog->comment->find() as $comment)
    {
        
    $comment->name 'something';
        
    $comment->text 'something';
        
    // .. ?
    }
    // ...
    $blog->save(); 

  10. #110
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    True, I just took that example since it was used previously. Hmm, so, say we need to update something in certain comments, how would you write that?
    What's wrong with:

    PHP Code:
    foreach ($blog->comment->find() as $comment)
    {
        
    $comment->name 'something';
        
    $comment->text 'something';
        
    $comment->save();
    }
    // ...
    $blog->save(); 

  11. #111
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Nothing but wouldn't you only need to call

    PHP Code:
    // ...
    $blog -> save(); 
    If you were only saving something that changed in regards to the blog entry it's self, rather than in regards to it's comments, or are you inferring that blog should save in any event?

  12. #112
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    What's wrong with:

    PHP Code:
    foreach ($blog->comment->find() as $comment)
    {
        
    $comment->name 'something';
        
    $comment->text 'something';
        
    $comment->save();
    }
    // ...
    $blog->save(); 
    Nothing wrong, but the "loaded" association (or whatever one should call it) doesn't get "updated", right? Which means you've got to run a new finder if you want to display/do more a bit later?
    PHP Code:
    foreach ($blog->comment->find() as $comment)
    {
        
    $comment->save();
    }
    // ...
    foreach ($blog->comment->find() as $comment)
    {
        echo 
    $comment->text;

    Say for instance that you send along the entire $blog object (including comments) to your view for rendering?

  13. #113
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    Nothing wrong, but the "loaded" association (or whatever one should call it) doesn't get "updated", right? Which means you've got to run a new finder if you want to display/do more a bit later?
    If you store the results of your find in an array, changes made while iterating through the array will be present the next time you iterate through the array, so you could simply do:

    PHP Code:
    $comments $blog->comment->find();

    foreach (
    $comments as $comment)
    {
        
    $comment->text 'foo';
        
    $comment->save();
    }
    // ...
    foreach ($comments as $comment)
    {
        echo 
    $comment->text;

    Quote Originally Posted by pakmannen View Post
    Say for instance that you send along the entire $blog object (including comments) to your view for rendering?
    Thing is, I consider it poor form to use the same action for modifying data and displaying result; I would have one action do the saving, then redirect to another action that does the displaying.

  14. #114
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    If you store the results of your find in an array, changes made while iterating through the array will be present the next time you iterate through the array, so you could simply do:
    PHP Code:
    $comments $blog->comment->find();
    ... 
    So the difference between doing that and doing:
    PHP Code:
    $comment = new Comment();
    $comment $comment->find();
    foreach (...) 
    Is only that your example takes care of the association condition automatically? (Linking the foreign keys)

    Thing is, I consider it poor form to use the same action for modifying data and displaying result; I would have one action do the saving, then redirect to another action that does the displaying.
    Fair enough, but I think you understand my point. But all right, let me see, do you store lazy loaded objects at all in the main object? Or do you only do that with eager loading?

  15. #115
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    So the difference between doing that and doing:
    PHP Code:
    $comment = new Comment();
    $comment $comment->find();
    foreach (...) 
    Is only that your example takes care of the association condition automatically? (Linking the foreign keys)
    It also uses the default parameters given when declaring the association. It's the middle ground between using a finder and relying on the default association results.

    Quote Originally Posted by pakmannen View Post
    Fair enough, but I think you understand my point. But all right, let me see, do you store lazy loaded objects at all in the main object? Or do you only do that with eager loading?
    I don't store lazy loaded has_many associations, but that's not because I don't think it's a good idea; it's simply an optimization that I haven't needed yet.

  16. #116
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hmm, I see. But one question, if these two do the same thing:
    PHP Code:
    $comment $blog->comment;
    $comment $blog->comment->find(); 
    How does that work? The first one is a getter that runs a finder (using the default values) and returns the result. The second one has this first getter but then also runs another finder on the result? Which uses the same default values? That doesn't make sense.. Or do you always use the $blog->comment->find() syntax for has_many?

    Also, in my case that doesn't even work because has_many associations are arrays containing active record objects. So I wouldn't be able to do $blog->comment->find() (comment in this case is an array). But I recall you mentioning that you didn't use arrays for this?

  17. #117
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    Hmm, I see. But one question, if these two do the same thing:
    PHP Code:
    $comment $blog->comment;
    $comment $blog->comment->find(); 
    How does that work?
    The way it would work is that the first returns a special association object; it implements a find method (which is used in the second example), and it implements array access, so that when you use it as an array, it'll run the default find query.

    Quote Originally Posted by pakmannen View Post

    The first one is a getter that runs a finder (using the default values) and returns the result. The second one has this first getter but then also runs another finder on the result? Which uses the same default values? That doesn't make sense.. Or do you always use the $blog->comment->find() syntax for has_many?
    The point of the ->find() syntax is to override the default values; let's say by default $blog->comment returns all the comments for the blog, in chronological order, $blog->comment->find(array('order'=>'posted_at DESC', 'limit'=>1)) would return you the latest one.

    Quote Originally Posted by pakmannen View Post
    Also, in my case that doesn't even work because has_many associations are arrays containing active record objects. So I wouldn't be able to do $blog->comment->find() (comment in this case is an array). But I recall you mentioning that you didn't use arrays for this?
    Actually I'm not currently using the syntax I'm describing, as my code was written to be PHP4 compatible; I'm basing my examples on Rails. What I use is $blog->find('comment', $conditions), but I'm currently planning a PHP5 rewrite which will implement what I'm describing.

  18. #118
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    The way it would work is that the first returns a special association object; it implements a find method (which is used in the second example), and it implements array access, so that when you use it as an array, it'll run the default find query.
    Ah, a special object! Okey but then I understand your code.

    The point of the ->find() syntax is to override the default values; let's say by default $blog->comment returns all the comments for the blog, in chronological order, $blog->comment->find(array('order'=>'posted_at DESC', 'limit'=>1)) would return you the latest one.
    Got it.

    By the by, anyone using Doctrine? I started to read their manual (thanks ahundiak) and it seems pretty nice. I dabbled a little in Propel a while ago but didn't really like all the generated code and stuff. Doctrine seems nicer.. I also noticed they're using the same directory structure as the Zend framework, so I guess there's a chance they play well together, which is a bonus.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •