SitePoint Sponsor

User Tag List

Results 1 to 6 of 6
  1. #1
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Using a unit of work with table data gateways for form processing

    We recently started doing quite a few multi-page forms that store data in a database in a way that can be extracted to some form (PDF, plaintext, etc). Anyway, we got quite a few large ones coming up, and I've been thinking of a way to refactor a bit and come up with a reusable solution.

    I've been thinking a bit of implementing a Unit of Work pattern to kind of hold on to several table row gateway objects, but a bit baffled how this should work. Essentially, I would like a collection of objects that do your normal update, insert, and delete, and have finder methods that, for example, know how to find a user by id, or by something like first name, last name, etc.

    See, I plan on storing each value submitted on the next page as a hidden input, and then drop everything down into the database when the last page is submitted. what I would like to do is pass all the variables to the unit of work to sort out. Perhaps the class could have some kind of linked list of table data gateways in the order their needed, has some kind of registry to determine what actions need to take place on each insert, and what needs to be gained (i.e. an id used for a foreign key mapping in a latter entry), etc.

    Anyway, anyone else implemented something like this? I'm using ADODB for the database backend as I tend to have it work with a multitude of databases, below is an example of a table data gateway I created for PersonalInfo:

    PHP Code:
    class PersonalInfoGateway
            
    {
            function 
    findByUID($uid)
                    {
                    
    $GLOBALS['adodb']->SetFetchMode(ADODB_FETCH_ASSOC);
                    
    $sql "SELECT appid, first_name, last_name, mi, 
                            street1, street2, city, state, zip,ssn, 
                            home_phone,other_phone, email, auth_us 
                            FROM app_personal_info
                            WHERE uid = 
    $uid";

                    
    $rs =& $GLOBALS['adodb']->Execute($sql);

                    
    /* query failed */
                    
    if(!$rs)
                      print 
    $db->ErrorMsg();

                    return 
    $rs->fields;
                    }

            function 
    insert($person$appid)
                    {
                    if(!isset(
    $appid))
                            die(
    'No user id set!');
                    
                    
    $sql "SELECT * FROM app_personal_info WHERE uid = -1";
                    
    $rs =& $GLOBALS['adodb']->Execute($sql);
                    
    $insertSQL =
                            
    $GLOBALS['adodb']->GetInsertSQL($rs,$person);

                    
    $GLOBALS['adodb']->Execute($insertSQL);

                    
    /* return the userid? */
                    
    }

            function 
    update($uid)
                    {
                    if(!isset(
    $uid)
                            die(
    'No user id set!');

                    
    $sql "SELECT * FROM app_personal_info WHERE uid = -1";
                    
    $rs =& $GLOBALS['adodb']->Execute($sql);
                    
    $updateSQL $GLOBALS['adodb']->GetUpdateSQL($rs,
                                                                 
    $person);
                    
    $conn->Execute($updateSQL);
                    }
            function 
    delete($uid)
                    {
                    
    $sql "DELETE FROM app_personal_info WHERE uid = $uid";
                    
    $GLOBALS['adodb']->Execute($sql);
                    }
            } 
    I'm basically looking for a way to abstract things and chop development time for future multi page forms down a bit. I mean, something like a form that just gets the info mailed is easy enough, but when storing the data in a database, things get a bit more complex as you have to follow all the rules and domain logic as well, and as we all know business logic changes often.

    I guess I just want some all in one solution... I've had alot of forms and while some data is different, alot is similar or the same (address, contact info, personal info, business info) and I'd like to find a way to reuse. I've created a few primitive data objects that I suppose I'll share as well:

    Partent
    PHP Code:
    class Primitive 
            
    {       
            function 
    Primitive()
                    {
                    die(
    'Is not called directly.');
                    }
            
            function 
    setData($data)
                    {
                    if(
    is_array($data))
                      foreach(
    $data as $key=>$value)
                        {
                         if((!
    is_null($value)) &&
                            (
    array_key_exists($key$this->_attrMap)))
                                {            
                                if(
    strstr('object'$this->_attrMap[$key]))
                                   {
                                    
    $object explode('.'$this->_attrMap[$key]);
                                    
    $method 'set'.$object[1];
                                    
    $this->$method($value);
                                    }
                                else
                                    
    $this->$key $value;
                                }
                          }
                      }
            
            function 
    getData()
                    {
                    
    $data = array();

                    foreach(
    $this->_attrMap as $key => $value)
                            {
                            if(!
    is_null($this->$key))
                                    {
                                    if(
    strstr($value'object'))
                                            {
                                            foreach(
    $this->$key->getData() as $k => $v)
                                                    
    $data[$k] = $v;
                                            }
                                    else
                                            
    $data[$key] =& $this->$key;
                                    }
                            }
                       
                    return 
    $data;
                    }

            } 
    Subclasses:

    PHP Code:
    class Person extends Primitive
            
    {
            var 
    $first_name NULL;
            var 
    $last_name  NULL;
            var 
    $mi         NULL;
            var 
    $ssn        NULL;
            var 
    $home_phone NULL;
            var 
    $work_phone NULL;
            var 
    $email      NULL;

            
    /* address object, not set directly */
            
    var $address    NULL;

            
    /* attribute map */
            
    var $_attrMap = array('first_name' => 'string',
                                                    
    'last_name'  => 'string',
                                                    
    'mi'         => 'string',
                                                    
    'ssn'        => 'string',
                                                    
    'address'    => 'object.Address',
                                                    
    'home_phone' => 'string',
                                                    
    'work_phone' => 'string',
                                                    
    'email'      => 'string');

            function 
    Person($person NULL)
                    {
                    
    $this->setData($person);
                    }

            function 
    setAddress($address)
                    {
                    if(
    is_object($address))
                            
    $this->address $address;
                    else
                            die(
    'setAddress() expects object of type Address.');

                    }
            } 
    PHP Code:
    class Address extends Primitive
            
    {
            var 
    $street1 NULL;
            var 
    $street2 NULL;
            var 
    $city    NULL;
            var 
    $state   NULL;
            var 
    $zip     NULL;
            var 
    $country NULL;

            var 
    $_attrMap = array('street1' => 'string',
                                                     
    'street2' => 'string',
                                                     
    'city'    => 'string',
                                                     
    'state'   => 'string',
                                                     
    'zip'     => 'string',
                                                     
    'country'  => 'string');

            function 
    Address($address NULL)
                    {
                    
    $this->setData($address);
                    }

            } 
    PHP Code:
    class Business extends Primitive
            
    {
            var 
    $company_name                  NULL;
            var 
    $owner                 NULL;
            var 
    $phone                 NULL;
            var 
    $fax                   NULL;

            
    /* address object */
            
    var $address NULL;

            var 
    $_attrMap = array('company_name'  => 'string',
                                                      
    'owner' =>  'string',
                                                      
    'phone' => 'string',
                                                      
    'fax'   => 'string',
                                                      
    'address' => 'object.Address');

            function 
    Business($business=NULL)
                    {
                    
    $this->address =& new Address;
                    
    $this->setData($business);
                    }

            function 
    setAddress($address)
                    {
                    if(
    is_object($address))
                            
    $this->address address;
                    else
                            die(
    'Method setAddress expects type Address.');
                    }

            } 
    I think you get the general idea. I'll probably have several private methods that do some data validation when setting data. Anyhow... suggestions please, thanks!

  2. #2
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I may need to elaborate a little bit more...

    For normalization, I've done things in my database like have a table of addresses, and refrence an address in a field in the company, person, etc tables. Further, things like the Income table, which stores answers to questions about income, belongs to an id for the person (as we have, say, an applicant, a joint applicant, etc). Further, each of the persons defined belong to a single application.

    I just want to have some kind of referential integrety, yet at the same time use a completely reusable object model.... just wondering what patterns may be appropriate?

    Thanks

  3. #3
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OOLaw,

    My reading of Unit-of-work is that you're already doing it, on the one hand, with your hidden fields, and that you're looking at it sideways on the other hand.

    The UW replaces a very-long-running transaction, so in this case it would batch up all of your various table updates. That part you've already got, especially since your data is highly normal.

    OTOH, collecting the data in hidden form fields obviates the need to do much else -- UW talks about having a "transaction" table/view that will be discarded if the commit isn't made.

    The one thing that stands out is that your transaction is a business entity of some kind. Perhaps the UW is the final data mapper that takes the (validated) entity and commits it against all the various tables.

    =Austin

  4. #4
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, as I understood a unit of work (probably got it wrong) was that it's an object that you attach objects to for it to work with I suppose. I was thinking of running the inputs first through an intercepting filter or sanitizer of some sort, then use the unit of work to "toss" the data that needs to be inserted through a sort of linked list of table data gateways in order to perform the transactrions in order, I suppose having some kind of array or something (perhaps a seperate object entirely) that knows what variables map to what tables, where they go, what they require, etc.

  5. #5
    Non-Member
    Join Date
    Oct 2004
    Location
    downtown
    Posts
    145
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    then use the unit of work to "toss" the data that needs to be inserted through a sort of linked list of table data gateways in order to perform the transactrions in order, I suppose having some kind of array or something (perhaps a seperate object entirely) that knows what variables map to what tables, where they go, what they require, etc.
    Why not implement the Intercepting Filter idea into the Unit of Work? What I mean is that each filter will handle each Table Data Gateway you'd have? So in the end the linked list would actually be the Intercepting Filter(s) it's self?

    The pre processing could handle the insertions, whilst the post processing could handle the commits, or maybe that should be the accumlation of commits? As the last Intercepting Filter would do the one (and only one) last commit it's self

    Hope I've explained clearly enough so you have an idea of where I'm going with this?

  6. #6
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yes, this is a general idea I've been having... perhaps the best way to implement the unit of work would be to have a class representing each section representing the application (i.e. I have section a, which contains groups of data such as personal info, address (current and previous), Employer, etc) and methods representing each subsection. Here's a general idea:

    PHP Code:

    class SectionA
       
    {
        ...

       
    // $personal_info is an array of personal info 
       
    function personalInfo($personal_info)
         { 
         
    $filter =& new personalInfoFilter($personalinfo);
         if(
    $filter->validate())
           {
           
    $this->pid PersonalInfoGateway::insert($filter->cleanData());
           }
         else
          {
          
    /* let the user know what went wrong,
          assumed that we want to completely 
          exaine all info from the page so we can 
          let the user know everything that went wrong */
          
    $this->errors $filter->getErrors());
          }
        }
        ...
      } 
    Just a general off the seat of my pants idea... trying to avoid defining the involved methods or objects.


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
  •