SitePoint Sponsor

User Tag List

Page 3 of 5 FirstFirst 12345 LastLast
Results 51 to 75 of 118

Thread: ActiveRecords

  1. #51
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    The easiest way to cache objects in php is to serialize them and save them in a text file. You can do something like this:
    Thanks for that! Never even thought about stuff like this. I'm gonna read up on caching one of these days..

    You can just use one method to return an array, and another to return a single object. I in fact don't use arrays, but an iterator which grabs the next row as required.
    Yeah, I guess I'll have some sort of check for this

    Now, about table associations and Joins and stuff. Am I correct in assuming you only use Joins for one-to-one or belongs-to relationships? Example, you have a Blog and a User. The Blog references a User with user_id. You perform a Join when fetching a Blog which results in:
    PHP Code:
    Blog Object
    (
       [
    id] => 100
       
    [user_id] => 201
       
    [blogtext] => Blah blah
       
    [name] => Bob # Joined from User table

    What about one-to-many relationships, such as a Blog and its Comments. Do you even WANT to fetch all Comments when finding a Blog? Or should you have a method in your Blog class called getComments() or something, that runs a comment finder? Something like:
    PHP Code:
    $blog findBlog();
    $comments $blog->getComments(); 
    Or would that lead to the 1+N problem 33degrees was talking about..?

    Now, if you do get all the Blog's comments when finding a Blog, do you put the comments in the Blog array, like this:
    PHP Code:
    Blog Object
    (
       [
    id] => 100
       
    [stuff] => Blah blah
       
    [comments] => Array(CommentObjects)

    Last edited by pakmannen; Sep 11, 2007 at 07:11.

  2. #52
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    Do you even WANT to fetch all Comments when finding a Blog?
    What do you want a Blog to do? It's hard to discuss code out of context. I don't know who the Blog clients are.

    Would you like to try some test-driven design? Testing can provide the missing context. As you test one class you'll discover secondary objects which the first needs to call upon in order to do its job. These secondary objects get mocked out then later you come back to implement them in tests of their own. The design ripples out in little steps from the point at which you started.

    It's a top-down process. Blog client tests will reveal all the details of the ways in which Blog objects will be used. This is where you evolve a Blog interface, in response to real client needs.

  3. #53
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, what I meant was more like: if you fetch all associated objects, doesn't that lead to a chain reaction where fetching a User fetches his Images which fetches its Comments etc..

    As for what a Blog could do: A list of Blogs might not need Comments, but when viewing a Blog you might want to list its Comments underneath the text. That's just an example, but you get the idea.

    I read a bit about Ruby's implementation and they have an option you supply to the finder called include. Basically:
    PHP Code:
    Blog::find(:all, :include => :comment); 
    Or something. So you'd specify when doing the finding exactly what related tables you want to fetch.

    Hope you understand what I'm talking about here..

  4. #54
    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
    Now, about table associations and Joins and stuff. Am I correct in assuming you only use Joins for one-to-one or belongs-to relationships?
    Not necessarily; rails, for example, does support eager loading of 1 to many associations. But this results in a LOT of data being pulled from the database at once, so it may not be worth it. The thing is that in all cases, the behavior is configurable, so you can chose to use it or not.

    Quote Originally Posted by pakmannen View Post
    Example, you have a Blog and a User. The Blog references a User with user_id. You perform a Join when fetching a Blog which results in:
    PHP Code:
    Blog Object
    (
       [
    id] => 100
       
    [user_id] => 201
       
    [blogtext] => Blah blah
       
    [name] => Bob # Joined from User table

    actually, the best thing to do is to keep the data in it's own object:

    PHP Code:

    Blog Object
    (
       [
    id] => 100
       
    [user_id] => 201
       
    [blogtext] => Blah blah
       
    [user] => User Object

    That way, you can turn the eager loading on and off without changes the code that pulls the information from the model. i.e.

    PHP Code:

    foreach($post in $posts) {
        print 
    $post->blogtext;
        print 
    $post->getUser()->name;


    This piece of code works regardless of whether the data came from a join or not.

    Quote Originally Posted by pakmannen View Post
    What about one-to-many relationships, such as a Blog and its Comments. Do you even WANT to fetch all Comments when finding a Blog? Or should you have a method in your Blog class called getComments() or something, that runs a comment finder? Something like:
    PHP Code:
    $blog findBlog();
    $comments $blog->getComments(); 
    The way I do it, getComments would return an array of results, so you would do this

    PHP Code:

    foreach($posts as $post) {
        
    // do stuff
        
    $comments $post->getComments();

        foreach(
    $comments as $comment) {
            
    // do stuff
        
    }


    Of course, the code required to do all this stuff is quite complex; in my experience the association stuff is the most complex part of a good Active Record implementation. But once it works, it makes development a lot faster.

  5. #55
    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 you'd specify when doing the finding exactly what related tables you want to fetch.
    Yes, as I said this behavior has to be configurable, as it won't always be beneficial to pull everything together

  6. #56
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    actually, the best thing to do is to keep the data in it's own object:
    PHP Code:
    Blog Object
    (
       [
    id] => 100
       
    [user_id] => 201
       
    [blogtext] => Blah blah
       
    [user] => User Object

    Ah, ok, so you'd do:
    PHP Code:
    $blog find();
    echo 
    $blog->blogtext;
    echo 
    $blog->user->name
    And if we were talking about a one-to-many it would be:
    PHP Code:
    Blog Object
    (
       [
    id] => 100
       
    [user_id] => 201
       
    [user] => User Object
       
    [comment] => Array (Comment Objects)

    But how would you ever be able to do that with a JOIN? If I join two tables they'll just be merged no? How would I be able to store a User object inside the Blog object using a join?

    The way I do it, getComments would return an array of results, so you would do this
    Yeah, that's what I meant also. But if you're using eager loading the query has already been performed, so getComments (or just foreach($blog->comment as $comment) would simply return an array of objects, not perform a query. Without eager loading the getComments function would perform a find query and then return an array of objects. That correct?

    Of course, the code required to do all this stuff is quite complex;
    No kidding?

  7. #57
    SitePoint Wizard Mike Borozdin's Avatar
    Join Date
    Oct 2002
    Location
    Edinburgh, UK
    Posts
    1,743
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    How would I be able to store a User object inside the Blog object using a join?
    Don't use one universal find() method for all the objects, write the special finder for the Blog object that does a dirty job of placing the User inside the Blog.

  8. #58
    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
    But how would you ever be able to do that with a JOIN? If I join two tables they'll just be merged no? How would I be able to store a User object inside the Blog object using a join?
    In mysql, it's pretty trivial to figure out which table a given results field comes from, allowing you to do this:

    PHP Code:
    function mysql_fetch_multi($result) {
        
    $row mysql_fetch_row($result);

        if(!
    $row) return false;

        foreach (
    $row as $offset => $value) {
            
    $return[mysql_field_table($result$offset)][mysql_field_name($result$offset)] = $value;

        }

        return 
    $return;


    Rails, on the other hand, seems to handle this by explicitly declaring all the fields to return, prefixing each with a table identifier; it could be because not all databases allow you to do what my code above does.

    Quote Originally Posted by pakmannen View Post
    Yeah, that's what I meant also. But if you're using eager loading the query has already been performed, so getComments (or just foreach($blog->comment as $comment) would simply return an array of objects, not perform a query. Without eager loading the getComments function would perform a find query and then return an array of objects. That correct?
    Yes, either way you're getting an array of objects, and your code works the same regardless of whether your were using eager loading or not. That way eager loading is simply an optimization that can be turned on or off, as needed.

    Keep in mind, though, that you might be better of optimizing elsewhere, rather than go through the trouble of implementing all of this; caching your resulting pages will always give you the most bang for the buck in terms of performance increases.

  9. #59
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    In mysql, it's pretty trivial to figure out which table a given results field comes from, allowing you to do this:
    Alright, nice!
    Yes, either way you're getting an array of objects, and your code works the same regardless of whether your were using eager loading or not. That way eager loading is simply an optimization that can be turned on or off, as needed.
    Yeah, think I understand.

    Keep in mind, though, that you might be better of optimizing elsewhere, rather than go through the trouble of implementing all of this; caching your resulting pages will always give you the most bang for the buck in terms of performance increases.
    Not that I really know anything about database performance, but reading this page http://ar.rubyonrails.com/classes/Ac...ssMethods.html (the part about eager loading anyway) you sort of realise how many unnecessary queries you could perform. In that example, fetching 100 posts results in 201 queries instead of 1, which would be the case using eager loading. 201 queries sounds like a lot for such a simple task but maybe that's a piece of cake for a database?

  10. #60
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    Rails, on the other hand, seems to handle this by explicitly declaring all the fields to return, prefixing each with a table identifier; it could be because not all databases allow you to do what my code above does.
    It's possible to do this with PDO (and as such, with many databases). I'm using an experimental method though, getColumnMeta(), so there is a chance it might not work with all drivers.
    PHP Code:
    foreach ($pdo->fetchAll(PDO::FETCH_ASSOC) as $row)
    {
        for (
    $i=0$meta $pdo->getColumnMeta($i); $i++)
        {
            
    $array[$meta['table']][$meta['name']] = $row[$meta['name']];
        }
        
    $result[] = $array;


  11. #61
    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
    It's possible to do this with PDO (and as such, with many databases). I'm using an experimental method though, getColumnMeta(), so there is a chance it might not work with all drivers.
    The documentation does state that it doesn't work with all drivers.

    Quote Originally Posted by pakmannen View Post
    PHP Code:
    foreach ($pdo->fetchAll(PDO::FETCH_ASSOC) as $row)
    {
        for (
    $i=0$meta $pdo->getColumnMeta($i); $i++)
        {
            
    $array[$meta['table']][$meta['name']] = $row[$meta['name']];
        }
        
    $result[] = $array;

    Note that if you're using PDO::FETCH_ASSOC, if you have more than one field with the same name you'll only get the last one in the results.

  12. #62
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    Note that if you're using PDO::FETCH_ASSOC, if you have more than one field with the same name you'll only get the last one in the results.
    Whops, just noticed that myself. This should work:
    PHP Code:
    foreach ($pdo->fetchAll() as $row)
    {
        for (
    $i=0$meta $pdo->getColumnMeta($i); $i++)
        {
            
    $array[$meta['table']][$meta['name']] = $row[$i];
        }
        
    $result[] = $array;


  13. #63
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PdoStatement implements Traversable. You can just do:
    PHP Code:
    foreach ($pdo as $row) {
    ... 
    If you want to specify the fetchmode, you can do:
    PHP Code:
    $pdo->setFetchMode(PDO::FETCH_ASSOC);
    foreach (
    $pdo as $row) {
    ... 
    On large datasets, this can preserve memory usage, if the database supports buffered queries (MySlq does).

  14. #64
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @kyber: Thanks for the tip, didn't know that. You wouldn't know about something similar to getColumnMeta which isn't experimental now would you? :P

    @33degrees: You said earlier that you keep your column info in a $columns array, that the save function loops through etc. Do you have a separate array for associated tables? Something like:
    PHP Code:
    Blog Object
    (
        [
    _columns] => Array
            (
                [
    id] = 100
                
    [user_id] = 201
                
    [blogtext] = Blah Blah
            
    )
        [
    _associated] => Array
            (
                [
    user] => User Object
                
    [comment] => Array(Comment Objects)
            )

    So the save function can save associated tables by looping thorough this array and run itsef. Or something.

  15. #65
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pakmannen View Post
    @kyber: Thanks for the tip, didn't know that. You wouldn't know about something similar to getColumnMeta which isn't experimental now would you? :P
    getColumnMeta() is a bit weird. I don't use it myself. Instead, I implement reflection on a per-driver basis. In practise, I use MySql 90% of the time, and SqLite for the remainder. Here's a snippet:
    PHP Code:
      /**
        * Returns reflection information about a table.
        */
      
    public function getTableMeta($table) {
        switch (
    $this->getAttribute(PDO::ATTR_DRIVER_NAME)) {
          case 
    'mysql':
            
    $result $this->query("SHOW COLUMNS FROM ".$this->quoteName($table));
            
    $result->setFetchMode(PDO::FETCH_ASSOC);
            
    $meta = Array();
            foreach (
    $result as $row) {
              
    $meta[$row['Field']] = Array(
                
    'pk' => $row['Key'] == 'PRI',
                
    'type' => $row['Type'],
              );
            }
            return 
    $meta;
          case 
    'sqlite':
            
    $result $this->query("PRAGMA table_info(".$this->quoteName($table).")");
            
    $result->setFetchMode(PDO::FETCH_ASSOC);
            
    $meta = Array();
            foreach (
    $result as $row) {
              
    $meta[$row['name']] = Array(
                
    'pk' => $row['pk'] == '1',
                
    'type' => $row['type'],
              );
            }
            return 
    $meta;
          default:
            throw new 
    pdoext_MetaNotSupportedException();
        }
      } 

  16. #66
    SitePoint Member
    Join Date
    Nov 2006
    Posts
    6
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    a fair extensive documentation of an PHP5 Active Record implementation:

    http://www.pradosoft.com/demos/quick...e.ActiveRecord

  17. #67
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by das7 View Post
    a fair extensive documentation of an PHP5 Active Record implementation:

    http://www.pradosoft.com/demos/quick...e.ActiveRecord
    Interesting. I haven't followed prado in a while, and hadn't seen version 3. From what I can see in the docs, Their active record implementation isn't bad, but it's a bit basic, IMO.

  18. #68
    SitePoint Member
    Join Date
    Nov 2006
    Posts
    6
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think active record was intended for relatively simple domain objects where the logic is simple and there is a close correspondance between the classes and table definitions. A data mapper provides a complete separation between the database and the domain objects. A data mapper is alot more complicated though. It should be noted that Active Record and Data Mapper are not mutually exclusive, i.e. they may be used to compliment each other.

  19. #69
    SitePoint Guru BerislavLopac's Avatar
    Join Date
    Sep 2004
    Location
    Zagreb, Croatia
    Posts
    830
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    My favorite has always been ADOdb, which has since long had support for PHP5 (exceptions, Iterators etc), and now is switching to PHP5 completely. It has an implementation of Active Record that I'm more than satisfied with.

  20. #70
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by das7 View Post
    I think active record was intended for relatively simple domain objects where the logic is simple and there is a close correspondance between the classes and table definitions.
    There does need to be a correspondence between classes and tables, but there's nothing preventing you from creating complex domain objects otherwise. Besides, I was comparing their implementations to others I've seen (and my own) when I said it it was basic.

  21. #71
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hey, 33degrees, what do you say about my previous post? About having associated tables in their own array?

    @Kyber: Thanks for that one, I'll check it out. Haven't noticed anything weird about getColumnMeta yet, but I'm on my toes..

  22. #72
    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
    Hey, 33degrees, what do you say about my previous post? About having associated tables in their own array?
    Sorry, missed that. What I have is an array of Association objects, which are basically specialized finders; when you request a property value, it first checks the array of stored properties, then the associations, which will run a query to return the associated models.

  23. #73
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

  24. #74
    SitePoint Enthusiast
    Join Date
    Aug 2007
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees View Post
    Sorry, missed that. What I have is an array of Association objects, which are basically specialized finders; when you request a property value, it first checks the array of stored properties, then the associations, which will run a query to return the associated models.
    Ah, so you're not using eager loading then?

    Another thing I thought about. My constructor for an ActiveRecord currently does something like this:
    PHP Code:
    __construct($columns)
    {
        foreach (
    $columns as $column => $value)
        {
            
    $this->_columns[$column] = $value;
        }

    That is, takes an entire array of properties/columns and adds them to the class member $_columns. What I was thinking was, maybe it should be enough to supply the id of a record to the constructor. That way you could populate a User object by specifying the User id. For instance:
    PHP Code:
    $user = new User($_SESSION['uid']); 
    In that case, I suppose the Finders should only ever select the id in a table. Of course, that becomes a problem when you've got tables without primary keys though. I dunno.. thoughts? Reason is I saw one implementation which did it like this, so I started to wonder.. :P Maybe I should just get on with it instead of checking how everybody else does things.. Argh

    Alternative is of course to do the above with a finder.
    PHP Code:
    $user User::FindById($_SESSION['uid']); 
    Last edited by pakmannen; Sep 17, 2007 at 04:55.

  25. #75
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Umm...

    > Of course, that becomes a problem when you've got tables without primary keys though.

    I don't know of any relational databases that allow you to have a table without a defined primary key first?

    Maybe someone knows of a relational database however that does not require a primary key? I can't understand why though, since pretty much every relation hinges on a primary key.

    > That way you could populate a User object by specifying the User id.

    That is a cleaner solution, than using this,

    PHP Code:
    $user User::FindById($_SESSION['uid']); 


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
  •