SitePoint Sponsor

User Tag List

Results 1 to 16 of 16
  1. #1
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)

    Updating columns that have been deselected

    In the loosely based ActiveRecord system I am building it is possible to deselect columns. If this is done then those columns will not be selected or included as properties of the returned objects.

    Take for example a simple scenario where a Project is found with a primary key of 8 while deselecting the summary field.

    PHP Code:
    $o Project::find('one',array('id'=>8,'limit'=>1,'deselect'=>'summary')); 
    In this case a dump of the returned object properties reveals that a summary property does not exist.

    HTML Code:
    Project Object
    (
        [_data:private] => ActiveRecordDataEntity Object
            (
                [_data:private] => Array
                    (
                        [id] => Array
                            (
                                [0] => 8
                            )
    
                        [name] => Array
                            (
                                [0] => Site Name
                            )
    
                        [weight] => Array
                            (
                                [0] => 8
                            )
    
                        [web_site] => Array
                            (
                                [0] => http://www.site_name.com/
                            )
    
                        [created] => Array
                            (
                                [0] => 2009-04-16 11:59:11
                            )
    
                    )
    
            )
    
    )
    This is not a problem until the object needs to be saved. When the object is saved only properties that have changed are updated. A changed property is defined by one which has a array count of more then one. Therefore, if the summary property is set on the previous object the summary in theory has not been changed.

    PHP Code:
    $o->summary 'Changed this summary'
    A dump of the object will reveal that array pertaining to the summary object only has one value.

    HTML Code:
    Project Object
    (
        [_data:private] => ActiveRecordDataEntity Object
            (
                [_data:private] => Array
                    (
                        [id] => Array
                            (
                                [0] => 8
                            )
    
                        [name] => Array
                            (
                                [0] => Site Name
                            )
    
                        [weight] => Array
                            (
                                [0] => 8
                            )
    
                        [web_site] => Array
                            (
                                [0] => http://www.site_name.com/
                            )
    
                        [created] => Array
                            (
                                [0] => 2009-04-16 11:59:11
                            )
    
                        [summary] => Array
                            (
                                [0] => Changed this summary
                            )
    
                    )
    
            )
    
    )
    Thus, the summary property has not been changed. Although, in theory it has been. So the only way to have a deselected field evaluate to being changed is to set it twice currently.

    PHP Code:
    $o->summary '';
    $o->summary 'Changed this summary'
    Now, a dump of the object will reveal that the summary property has changed.

    HTML Code:
    Project Object
    (
        [_data:private] => ActiveRecordDataEntity Object
            (
                [_data:private] => Array
                    (
                        [id] => Array
                            (
                                [0] => 8
                            )
    
                        [name] => Array
                            (
                                [0] => Site Name
                            )
    
                        [weight] => Array
                            (
                                [0] => 8
                            )
    
                        [web_site] => Array
                            (
                                [0] => http://www.site_name.com/
                            )
    
                        [created] => Array
                            (
                                [0] => 2009-04-16 11:59:11
                            )
    
                        [summary] => Array
                            (
                                [0] => Changed this summary
                                [1] => 
                            )
    
                    )
    
            )
    
    )
    The summary array has more then one value so the system will deem that it has been changed and include it upon saving the object.

    However, I rather have the system know that something has changed without this hack. I'm not really sure how to approach this though since the has changed methodology is based on the idea that there is more then one value in memory for a specific property.

    Any ideas or recommendations appreciated.

  2. #2
    SitePoint Addict
    Join Date
    Aug 2007
    Posts
    365
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have a seperate array for updates, and use references to point the $Data array to the update Array()

    PHP Code:
    class ..
    {
        private 
    $Data = array();
        private 
    $Updates = array();

       public function 
    Set$Name$Value)
       {
          
    $this->Updates$Name ] = $Value;
          
    $this->Data$Name ] = & $this->Updates$Name ];
       }
      
       public 
    funciton Save()
       {
           if( ! (
    count$this->Updates) > 0))
                 return ;

           
    $sql 'Update table Set'
           
    foreach( $this->Updates as $iName$iVal)
               
    $sql .= $iName '=' $iVal ;

           
    $sql ' Where id=' $this->PrimaryKey;
           
    DBUpdate$sql);
       }


  3. #3
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Any ideas or recommendations appreciated.
    Just some ideas:

    1. Keep the original version of the object in an identity map. Compare with modified object, update difference.
    2. Query for the original object, compare as above.
    3. Keep a flag for each property that is set when modified. When updating, only update fields that have a modified flag.

    These are all getting kinda tricky to be using with ActiveRecord though... might want to move up to a DataMapper.

  4. #4
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Quote Originally Posted by allspiritseve
    Keep the original version of the object in an identity map.
    Quote Originally Posted by allspiritseve
    Query for the original object, compare as above.
    Defeats the purpose of omitting the field.

    Quote Originally Posted by allspiritseve
    Keep a flag for each property that is set when modified. When updating, only update fields that have a modified flag.
    This seems the most reasonable and is what I was thinking. However, I'm not really certain how to implement it. Then again I haven't really been thinking about it to long… just today some here and there.

    Quote Originally Posted by allspiritseve
    ActiveRecord though... might want to move up to a DataMapper.
    The primary reason that the data containers aren't entirely separate from the logic is so that they can be used as a Gateway. Which means that every instance is a generic gateway typed to class which it represents. This isn't the concern though and works perfectly as is.

    PHP Code:
    $blog = new Blog(39);
    $comments $blog->getComments(array('include'=>'user')); 
    Quote Originally Posted by taliesinnz
    I have a seperate array for updates, and use references to point the $Data array to the update Array()
    PHP Code:
    class ..

    {

        private 
    $Data = array();

        private 
    $Updates = array();



       public function 
    Set$Name$Value)

       {

          
    $this->Updates$Name ] = $Value;

          
    $this->Data$Name ] = & $this->Updates$Name ];

       }

      

       public 
    funciton Save()

       {

           if( ! (
    count$this->Updates) > 0))

                 return ;



           
    $sql 'Update table Set'

           
    foreach( $this->Updates as $iName$iVal)

               
    $sql .= $iName '=' $iVal ;



           
    $sql ' Where id=' $this->PrimaryKey;

           
    DBUpdate$sql);

       }


    Won't work because then there needs to a distinction. I'm not talking about the way the Save works. I'm just talking about resolving properties of the data container as changed. To the data container there isn't a difference because it is just a repository. The data repository doesn't have knowledge of the Save class or implementation. The object used to save items is completely separate.

    PHP Code:
    $blog1 = new Blog(10)
    $blog1->entry 'changed title';

    $blog2 = new Blog();
    $blog2->entry 'new blog title';

    $save = new ActiveRecordSave();
    $save->add($blog1)->add($blog2);
    $save->query($db); 
    There isn't any SQL or queries being directly generated or executed inside the ActiveRecord class.

    The save object will then ask the ActiveRecord(IActiveRecordDataEntity) through its hasChanged() interface method if a property has changed. If that method returns true for any given model property then the item will be added to the update linked list and handled appropriately.

  5. #5
    SitePoint Addict
    Join Date
    Aug 2007
    Posts
    365
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Won't work because then there needs to a distinction. I'm not talking about the way the Save works. I'm just talking about resolving properties of the data container as changed. To the data container there isn't a difference because it is just a repository. The data repository doesn't have knowledge of the Save class or implementation. The object used to save items is completely separate.
    Prehaps I am reading your question wrong. Everything in the Update array has been changed, so cant you just check is see if there is in the Updates Array?

  6. #6
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Properties aren't interpreted as being updated until the save() method is called. At which point another class called ActiveRecordSave is responsible for separating updates, inserts, building queries and and executing the final SQL statements(s). None of this happens inside the data container which indirectly resides inside any ActiveRecord instance as a property. The data containers have no knowledge of what a "update" is. They just act like a repository for data. There is no concept of a update until there needs to be a separation for query building purposes.

  7. #7
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    This method is part of the ActiveRecordUpdate class and determines whether or not a record will added to the update list and which update list if any to add it to or create a new update list. The method recordHasChanged() determines whether or not a record has changed based on whether or not the ActiveRecord method hasChanged($field) returns true or not for any property native to the related model of the ActiveRecord.

    PHP Code:
        public function add(ActiveRecord $pRecord,$changed=false) {
        
            if(
    $changed===false && $this->recordHasChanged($pRecord)===false) return;
        
            if(
    is_null($this->record)) {
                
                    
    $this->record $pRecord;
        
            } else if(
    $this->isCompatible($pRecord)===true) {
            
                
    $this->records[] = $pRecord;
            
            } else {
            
                if(
    $this->hasSibling()) {
                
                    
    $this->getSibling()->add($pRecord,true);
                
                } else {
                
                    
    $this->setSibling(new ActiveRecordUpdate($pRecord));
                
                }
            
            }
        
        } 
    This method also resides inside the ActiveRecordUpdate class and is responsible for determining what specific fields to update along with gathering each fields most recent data.

    PHP Code:
        public function collectData() {
        
            
    $config ActiveRecordModelConfig::getModelConfig(get_class($this->record));
            
            if(
    $config->hasFields()===true) {
            
                foreach(
    $config->getFields() as $field) {
                
                    if(
    $this->record->hasChanged($field)===true) {
                    
                        
    $this->collectFieldData($field);
                    
                    }
                
                }
            
            }
        
        } 

  8. #8
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Maybe the solution could be something like this?

    PHP Code:
    class Blog {

        protected 
    $suppress;
        
        protected 
    $changed = array();

        public function 
    suppress($bool) {
        
            
    $this->suppress $bool;
        
        }

            ......

    }


    $record = new Blog();

    $record->suppress(true); 

    $record['id'] = $row['t0_id'];
    $record['title'] = $row['t0_title'];
    $record['entry'] = $row['t0_entry'];
    $record['created'] = $row['t0_created'];
    $record['subtitle'] = $row['t0_subtitle'];

    $record->suppress(false); 

    $record['status'] = 0

    The suppress property would determine whether or not to log a change. So when the record is filled with data from a result set changes are suppressed. However, after the record has been filled all changes are unsuppressed. So any alteration to a property is logged.

    In the above case status would be the only property present in the changed array.

    This seems like the simplest approach.

    This would also make it easy to change associated records since the user property would be logged as changed.

    PHP Code:
    $record['user'] = new User(7); 
    The otherwise a user would need to be included then changed so that there were 2 users in memory for the given record.

    Just thinking if this will break anything I currently have working. Otherwise it seems like a good solution.

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    I'm having some trouble deciding which class the responsibility of logging changes and handling suppression falls under.

    The ActiveRecord class and ActiveRecordDataEntity class both implement the interface IActiveRecordDataEntity. This interface is shown below. If something is a instance of IActiveRecordDataEntity the it can be hypothetically overloaded. IActiveRecordDataEntity essentially provides a common interface for objects that act as a dynamic repository for data.

    PHP Code:
    interface IActiveRecordDataEntity extends IteratorAggregate {

        public function 
    getProperty($pName);
        public function 
    setProperty($pName,$pValue);
        public function 
    hasProperty($pName);
        public function 
    hasChanged($pName);
        public function 
    addRecord($pPropertyName,IActiveRecordDataEntity $pRecord,$pArrayByDefault=false);
        public function 
    getRecord($pPropertyName,$pPrimaryKey,$pField);
        public function 
    removeProperty($pPropertyName);
        public function 
    getData();
        public function 
    cast();
        public function 
    suppress($bool); // to keep everything compatible I'm thinking


    A ActiveRecord decorates ActiveRecordDataEntity.

    With that said, is it the ActiveRecords responsibility to log changes to properties or would that more or less be the responsibility of the ActiveRecordDataEntity?

    If this is the responsibility of the ActiveRecord then the suppress method will be empty for all ActiveRecordDataEntity instances. However, if it is the responsibility of the ActiveRecordDataEntity then the implementation of hasChanged() would need to be altered for the ActiveRecordDataEntity. Yet, by making this the ActiveRecords responsibility the hasChanged() method can be overridden and changes can be logged before pushing them through to the decorated object. This seems like the correct approach.

    In this case the suppression functionality would only be available to instances of ActiveRecord even though ActiveRecordDataEntity has a suppress method which would just be empty to make sure the interface stays the same between instances of IActiveRecordDataEntity.

  10. #10
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Maybe the solution that would work well for fetching result sets is to create a ActiveRecordDataEntity – load it with data – then create a ActiveRecord with that data. Where the active record adds the functionality of suppression and property change logging.

    PHP Code:
    $records = new ActiveRecordCollection();
    while(
    $row $stmt->fetch(PDO::FETCH_ASSOC)) {

         
    $entity = new ActiveRecordDataEntity();
         
    $entity->setProperty('title',$row['t0_id']);
         
    $entity->setProperty('title',$row['t0_title']);
         
    $entity->setProperty('created',$row['t0_created']);

         
    $records->add(new Blog($entity));

    This way the suppression method could be eliminated.

  11. #11
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PDOStatement::fetchObject() works well here. The object is created by PDO, but the properties are set before the constructor is run. Therefore, if the constructor initialises the updates array (to empty), setting of those properties will not be logged because that constructor will "undo" anything the Set() method did previously. The same would happen if you used the magic __set() method: it is run for each field retrieved by PDOStatement::fetchObject() before the constructor.

    I've played with this a lot. It works a treat!
    Zealotry is contingent upon 100 posts and addiction 200?

  12. #12
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    The previous method I posted is working well. It only required a change in the implementation not the interface. Exactly, what i was looking for.

  13. #13
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    fetchObject wouldn't work because a ActiveRecord instance isn't represented by a row. Each row could represents 1,2,3,4… separate ActiveRecord instances that are either parent or children of one another.

  14. #14
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    example of what needs to happen recursively to create the appropriate object hierarchy.

    result set:

    Code:
       t0_id   |  t0_title  |   t1_id   |   t1_name
    ----------------------------------------------------
         1     |    'a'     |     2     |   'tom67'
         3     |    'd'     |     4     |    'mark45'
         9     |    's'     |     2     |    'tom67'
    Collecting the result one-dimensionally would result in a conflict between columns and more importantly a incorrect hierarchy.

    ActiveRecord
    -- t0_id
    -- t0_title
    -- t1_id
    -- t1_name

    t1 is a child of t0 therefore, this needs to represented at the domain level. The native PHP objects and methods don't provide this type "hydrating" for the result set.

    ActiveRecord
    -- t0_id
    -- t0_title
    -- ActiveRecord
    ----- t1_id
    ----- t1_name

  15. #15
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    ActiveRecord
    -- t0_id
    -- t0_title
    -- ActiveRecord
    ----- t1_id
    ----- t1_name
    Oh, I see.

    It is possible to this with PDOStatement::fetchObject() if you use the magic __set() method. You'd need some code to evaluate the property name (such as "t1_id") to determine whether it's a native property of this created object or a "child" property. You can create the child as necessary.

    I have done this too, but it does blow out the __set() method. You may consider it's not worthwhile especially since you seem to have found a solution.
    Last edited by auricle; May 28, 2009 at 03:31. Reason: typographical error
    Zealotry is contingent upon 100 posts and addiction 200?

  16. #16
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,135
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    I appreciate the advice and input. However, the goal at this point is to change the code as little as possible with each new enhancement added. I have about 35 classes and 10 interfaces to manage. Therefore, I'm trying to avoid any major changes unless they are warranted. The last thing I need to happen is for something to break that already works well.


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
  •