ExpressionEngine CMS
Open, Free, Amazing

Thread

This is an archived forum and the content is probably no longer relevant, but is provided here for posterity.

The active forums are here.

DataMapper 1.6.0

September 05, 2008 12:32pm

Subscribe [115]
  • #106 / Oct 08, 2008 6:44am

    Paul Apostol

    43 posts

    @stensi thank you very much, you’re THE BEST

  • #107 / Oct 09, 2008 12:06am

    OverZealous

    1030 posts

    Wow - I’m very impressed with how much you’ve done with a very small file (and simple setup/upgrades).

    I’m still working on creating my application, but I’ve come across a few things I needed to modify that made DataMapper much more useful:

    /**
     * Get Once
     * 
     * Gets this object if it hasn't already been gotten.
     * Very useful for relationships.
     *
     * @access public
     * @returns $this
     */
    function get_once() {
        if( empty($this->id)) {
            $this->get();
        }
        $return $this;
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Get By Id
     * 
     * Gets an object by id.
     *
     * @access public
     * @param int id of object
     * @returns $this
     */
    function get_by_id($id) {
        $this->where('id', $id);
        return $this->get();
    }
    
    // --------------------------------------------------------------------
    
    /**
     * is
     * 
     * Checks to see if a field is true.
     * Automatically corrects PostGreSQL Booleans
     *
     * @access public
     * @param string the field name
     * @returns $this
     */
    function is($field) {
        $value = FALSE;
        if(! empty($this->{$field})) {
            $value = $this->{$field};
            if(! is_bool($value)) {
                $value = ($value == 't');
            }
        }
        return $value;
    }
        
    // --------------------------------------------------------------------
    
    /**
     * isnot
     * 
     * Checks to see if a field is false.
     * Automatically corrects PostGreSQL Booleans
     *
     * @access public
     * @param string the field name
     * @returns $this
     */
    function isnot($field) {
        return !($this->is($field));
    }

    For now, I’ve added them to a subclass, which I’m then subclassing for my models.  I don’t know how useful they are for you, but you might want to add them (at least, the get_once and get_by_id methods).  The other two are nice for helping with PostGreSQL Booleans, which return ‘t’ or ‘f’.  They read nicely:

    $user->is('admin')

    Also, I have a concern with the save() function on new records.  Right now it loads the id in by matching all columns.  However, I can already see in a complicated system where the two objects might share the exact same information (such as two different users storing the same piece of information in the database).  In this case, the returned id may or may not be the correct one.

    Instead, it might be wise to use the provided $this->db->insert_id() method.  This should work well, especially if used within a transaction.

    Finally, is there any built-in support for transations?  Or do I just need to wrap the DataMapper models in a transation?

    NOTE: These functions have not been thoroughly tested yet, use at your own risk!

  • #108 / Oct 09, 2008 12:29am

    OverZealous

    1030 posts

    One more question.  I sometimes have additional information, such as which contact out of a list is “primary”, that must be stored within the relationship.

    A better example:

    Tasks have many Users
    Users have many Tasks
    Each Task has a primary User

    Is there any clean way to store this within a DataMapper setup?  (Meaning, I don’t want an extra relationship with an extra subclass of Users just to store which user is primary.)  Normally, I would include a boolean on the joining table.

    - Phil DeJarnett

  • #109 / Oct 09, 2008 6:43am

    stensi

    109 posts

    Thanks Phil. I can see where those is and is_not methods would come in handy for those using PostgreSQL.  I’m a MySQL and Microsoft SQL user so never realised PostgreSQL stored Booleans that way.  Almost like it’s a MySQL ENUM.

    If possible, I’ll see if there’s a way for me to detect if a PostgreSQL Database is being used, and then convert t/f fields to proper PHP Booleans so you can used them as normal, ie:

    if ($u->admin) { ... }

    I’m not sure about the get_once method.  What situation would you need to use that in?  I use the DataMapper in a way that makes sure I’m only getting once for each object I’m accessing.

    I originally planned on having the get method accept an optional integer and get by id using that, but decided to make it use limit and offset instead so it would be more similar to the CodeIgniter ActiveRecord’s get method.  So yes, the slight downside of that is that getting by id requires a bit more code.  get_by_id would be a handy addition for those who prefer to call in a simpler way, than having to specify the where clause.  I’ll consider putting that in the next version.

    As for you last question, about the a task having a primary user, I’ll have a think about it and get back to you.  Been a long day 😉

  • #110 / Oct 09, 2008 1:07pm

    OverZealous

    1030 posts

    I’m using the get_once() in several ways.  The main place I find it useful is when dealing with somewhat complex relationships.

    (I’ve added a check to only perform the get() if the related field is not empty.  This way it doesn’t get run on uninitialized Models.  A non-related model should already be populated!)

    Say a Company has many Users.  To check to see if a User can log in, I need to lookup the user’s info, of course.  If nothing matches, or if the User has been disabled, I can stop.

    However, once I lookup the User, I also need to check to see if the Company is enabled (or has been paying their bill 😉 ).  So, I do a get_once().  This way, I only load it if I need to.

    After a successful login, if I need to access the Company, I can call get_once() safely, and know I’m not re-checking the database again.

    Similarly, I use it when walking the object hierarchy (see below).

    Since my last post, I’ve also come up with another simple method:

    /**
     * Get Clone
     * 
     * Creates a clone of this object, and returns it
     *
     * @access public
     * @return clone, or NULL if id has not been set
     */
    function get_clone() {
        $clone = NULL;
        // try to get this object, if it is in a relationship
        $this->get_once();
        if( ! empty($this->id)) {
            $m = ucfirst($this->$model);
            $clone = new $m();
            $clone->get_by_id($this->id);
        }
        return $clone;
    }

    I use this for performing relationship lookups when I don’t want to modify an existing object.  Say I have a list of Phonenumbers for a Client.  Only one of these is the default:

    // in Phonenumber
    function set_default()
        $c = $this->client->get_clone();
        $others = $c->phonenumber->where('default', TRUE)->get();
        foreach($others as $item) {
            $item->default = FALSE;
            $item->save();
        }
        $this->default = TRUE;
        $this->save();
    }

    See how it lets me nicely back-up the hierarchy and grab an ancestor.  Of course, it could easily be used for going down the hierarchy, as well.

    Finally, if you are interested, I have some additional validation rules that are fairly generic.  I have several for parsing and comparing dates and timestamps, and one for cleaning up currency inputs (strips starting and ending non-numeric characters).

    I understand being busy - thanks for working so hard on DataMapper!

    - Phil DeJarnett

  • #111 / Oct 09, 2008 1:41pm

    Maxximus

    55 posts

    Hi Phil, Validation is neat indeed, and I personally would like to see your date rules. What did you do, added them to DM, or keep them in a separate file?

  • #112 / Oct 09, 2008 1:43pm

    BaRzO

    105 posts

    Me too 😉

  • #113 / Oct 09, 2008 3:25pm

    OverZealous

    1030 posts

    My goal is to make upgrades drag-and-drop - so I never edit DataMapper.  In fact, I just created a DataMapper subclass — DataMapperExt — and used that to create all of my Models.  I think it should be a recommended pattern, even if you don’t extend DataMapper.  It allows for easy tweaks whenever you need.

    My DataMapperExt contains my $prefix and $join_prefix (I like these a lot!), as well as overriding $error_prefix, $error_suffix, and the timestamp field names, so I wouldn’t have to update these for every model.

    Oh, it also loads in the language files for these checks.  I’m not sure if I’m doing it right 😊

    My validation rules include:

    valid_date: checks to see if a date is valid.  Accepts anything that strtotime() accepts.  Stores the value as ‘Y-m-d’, and stores the timestamp (as an int) as well.
    valid_timestamp: Same as above, but includes time.  Format is ISO8601.
    date_is_before: Checks to see that a date is before another date.  Must call valid_date first.
    date_is_after, date_is_on_or_before, date_is_on_or_after: same as above, but obviously different comparisons.

    fix_currency: Strips everything that isn’t a digit, period, or comma from the start and ends of a value.  Ex: $4.50 -> 4.50; €2,45 -> 2,45; 100USD -> 100

    I’ve attached datamapperext.php, as well as datamapperext_lang.php to this message.  Feel free to hack it up.

    UPDATE: I tweaked the code to handle parsing date fields that are not yet validated.  See _get_timestamps().

    UPDATE 2: Sorry if this caused problems for anyone - I didn’t realize it wouldn’t even parse :ohh:  Try the newer version.  Also added is_new, which is used to see if an object has not been instantiated yet.  Useful for returning objects instead of NULLs.

  • #114 / Oct 09, 2008 6:04pm

    stensi

    109 posts

    Great stuff Phil 😊

    I see how useful those get_ methods can be now.  Thanks for explaining!  If it’s alright with you I’ll look at adding this functionality into the next version of DataMapper, along with those handy validation rules.

    I’ve been meaning to add the option to allow developers to choose whether they want the Automated Timestamp handling as DateTime or Unix Timestamp so I’ll be doing that as well.

    Also, with some convincing from Maxxiumus and wiredesignz, I’ve made the decision to convert DataMapper into a library.  Don’t worry, this should have basically no affect on existing models and code using DataMapper, as it simply means the DataMapper.php file will be moved over to the application/libraries folder.  I’ll also be including a config file, which is automatically loaded into DataMapper, so for those who prefer to set their global DataMapper options in a config file, rather than directly in the class, they can!

    By the way, your approach of extending DataMapper with a subclass containing your own global rules and methods, then having your proper DataMapped models extend the subclass, is a good idea.  It means that whenever there’s a new version of DataMapper, you can drop the new version straight in without having to re-merge any changes you’ve made, as apposed to if you had been modifying DataMapper directly.

    And on that last question of yours I was going to get back to you on, I’ll be running some tests, hopefully today, to see whether its possible in the current version.

  • #115 / Oct 09, 2008 6:07pm

    Maxximus

    55 posts

    @ Phil: Cool! thanks a lot, always good to see how others make their life a little easier. Think these additional rules will prove to be handy to me (and others)!

    @stensi: Great! Yeah, the way to extend the class is a good solution indeed.

  • #116 / Oct 09, 2008 6:18pm

    OverZealous

    1030 posts

    That’s awesome.  Please do include my features if you think they are ‘worthy’!  I’ve been using the get_[whatever] pattern all over.  I often have get_visible() or get_enabled() for items that can be hidden or disabled.  These just add a where() into the stream.  It’s awesome as in $user->tasks->get_today().

    I have one other object I meant to share.  It’s still a prototype, and I haven’t begun to use it yet, but here’s the gist:

    I often have a collection of items that are sorted by an arbitrary field.  I want to be able to get this collection sorted, update the sort order, and insert new items in a consistent manner.

    So, I subclassed my DataMapperExt (again) and added functions to handle most of this.  I overrode the get and save functions to handle inserting new items, and retrieving the set sorted.  You usually only have to interact with one method:

    update_order: Pass in an array of ids, and it will look through the table and update the order to match the array.

    It’s customizable, so if you have a table that is sorted based on a grouping, you can easily override 2 functions and keep everything else.

    Example of this last part:
    Say you have a table of WidgetTypes, each of which belongs to a WidgetCompany.  Each WidgetCompany’s WidgetTypes are sorted independently.  (Note: this only works if each WidgetType has a unique WidgetCompany, meaning a one-to-many relationship.)

    WidgetType simply needs to subclass SortableModel and have a sort_field (by default, sortorder).  Then override the two functions next_sort_index and bump_sort_index, which retrieve the next sort index, and bumps every item up, respectively.  These functions will need to be rewritten to filter based on the WidgetCompany.

    OK, just had an inspiration.  Now those two methods above can be called with a model as the argument.  To filter, simply override, create the filtered model, and call parent::next_sort_index($my_model).  I’ll work on providing better examples at some point.

    EDIT: Please don’t use the attached file.  I’ve had to do a lot of reworking on this concept, and it isn’t ready for public consumption yet.  It might work on whole-table sorts, but that’s about it.

  • #117 / Oct 09, 2008 7:58pm

    OverZealous

    1030 posts

    I found two pretty serious bugs in the latest release:

    1) You cannot delete items now, because the delete function is not using the _get_relationship_table function, it generates the wrong table name.

    2) If you perform a select_max or select_min, the _to_object method throws an error, because it is looking for fields that do not exist.  Simply wrapping the $item->{$field} = $row->{$field}; in an if(isset($row->{$field})) { } appears to fix this.

  • #118 / Oct 09, 2008 8:21pm

    OverZealous

    1030 posts

    Also, there appears to be a bug with inserts on PostGreSQL.  PG is throwing an error about the ‘id’ field — which is a SERIAL (auto_increment) — being set to ‘’ (empty string).  I checked, and the SQL is definitely sending ‘’ as the ‘id’ field.

    To solve, I added unset($data[‘id’]); immediately before the insert function (inside save()).

    UPDATE: Apparently the problem is more general.  It’s trying to set fields that just aren’t set within the object.  I’m looking into it.

  • #119 / Oct 09, 2008 8:27pm

    OverZealous

    1030 posts

    One last thing (what’s wrong with me 😛):
    Don’t worry about the PostGreSQL booleans.  At this point, the best way to handle booleans in PGSQL is to set them as smallints.  Otherwise, it’s even harder to store and compare the values.  smallints almost work without any change.

    The bug, in my opinion, is within the PHP driver, because I’ve never had trouble with the JDBC drivers.

  • #120 / Oct 09, 2008 9:26pm

    stensi

    109 posts

    1:
    Thanks for pointing the delete issue out.  I hadn’t noticed that I didn’t switch the delete() method over to use _get_relationship_table().  This looks like it will only affect people using prefixes.

    2:
    I’ll have a look at select_min/max/avg/sum to see what you mean.  The likely fix will be to set as NULL if not set, so those working with Database fields that use NULL’s can go ahead fine.

    if (isset($row->{$field}))
    {
        $item->{$field} = $row->{$field};
    }
    else
    {
        $item->{$field} = NULL;
    }

    With the issue you’re having for the insert (id being supplied as an empty string), that looks to be another one of those PostgreSQL only issues, since MySQL ignores the empty string in my tests (id setup as a AUTO INCREMENT, BIGINT).

    _________________________

    UPDATE

    Just a note for everyone.  The next version of DataMapper will be as a library.  I’m making some changes to simplify a few things.  The most notable will be in setting up the $has_many and $has_one relationships.  Instead specifying the model => table, like so:

    $has_one = array("country" => "countries", "group" => "groups");
    $has_many = array("game" => "games");

    You’ll now just specify the model names:

    $has_one = array("country", "group");
    $has_many = array("game");
.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases