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]
  • #46 / Sep 27, 2008 9:44pm

    stensi

    109 posts

    I can change back to the default behaviour in the currently released version, that it auto-populates the related objects if they’re not already populated.  If already, populated it returns the existing data in it.

    It’s going to be very difficult to allow both ways though.  The reason I was changing it so you have to manually populate it was so it wouldn’t automatically fill with all records.  The best I think I can do is limit the auto-populate some how, maybe with just 1 record? ...

    If I get it working that way, it’ll make it easier for me in terms of the documentation 😊 since the existing code will still work.  The new version on its way will then basically just allow you to fine tune your related objects population if desired, otherwise they work like they currently do.

    I don’t think I’ll be able to chain custom objects like your last snippet there.  I’ll have a look into it though, but I expect that’s going to be a bit tricky to pull off.

    For now, the stuff I’m finishing off in order are:

    - Better loading/handling of Related Objects.
    - Allow validation of non-Database Table fields and allow custom field labels for all error messages.
    - Manual Transactions for InnoDB or BDB Database Tables (if I have time, give the option to automate the transactions).

  • #47 / Sep 27, 2008 11:17pm

    GregX999

    39 posts

    The “->all->get()” works well enough for me. I was just thinking of have the “->all->get()” be the default behavior for a relation that has yet to be populated.

    So that:
    $a->book

    Would be the same as:
    $a->book->all->get();

    But you could still do stuff like:
    $a->book->where(‘pages > 100’)->get();
    or
    $a->books;

    But that’s not important. I just thought it might be a neat thing.

    Which reminds me of something I forgot earlier… It would be nice if you could name relationships so that you could use $a->books instead of $a->book. Or if you had a Classified_Ad model you could use $member->ads instead of $member->classified_ad.

    Oh, and so models can reference themselves. For example, a “Employee” could have one “Supervisor” and have many “Underlings”, both of which are also Employees.

    Greg

  • #48 / Sep 28, 2008 12:18am

    stensi

    109 posts

    Just for clarification, it’s the other way round (related_object->get()->all) if you want to populate then access the all array.

    The reason I was changing it to not auto-populate by default, was because of memory concerns, such as it auto-populating with 100,000 records when the developer most likely wont need that many, so forcing them to choose to get all or narrow it down is better for them.  Still, if they don’t populate it, I’ll see if I can default it to populate with all, when you try and access a property.  Can’t make any promises that I’ll be able to pull that off.  Not having much luck so far 😕

    UPDATE
    Still not having luck with the auto-populate if not populated thing.  Mainly because of the chaining, since when a __get kicks off from when a related object is accessed, I have no way of telling if the next part of the chain is ->all, which if its empty I should auto-populate, or if it’s a ->where or ->get() etc, which means an auto-populate is not needed since the developer is doing one.

    I might have to just add a boolean setting that all related objects are auto-populated on first access if set to TRUE.  If FALSE, you have to manually populate it yourself (better performance wise if you’ve got lots of records and don’t want them all loaded). I’ll be defaulting to FALSE.
    ______________________

    Sorry but I wont be changing it to allow different names for the relationships.  The reason it’s the singular is because accessing $a->book is accessing the first book object and $a->book->all is an array of all the book objects returned.  This is how it should be for the DataMapper pattern, and keeping things similar to the usage shown at DataMapper.org.

    // Get first author object
    $a = new Author();
    $a->get(1);
    
    // Populate related object with a maximum of 10 objects
    $a->book->get(10);
    
    // Show properties of the first related book
    echo $a->book->id . '
    ';
    echo $a->book->name . '
    ';
    echo $a->book->description . '
    ';
    
    // Show properties of all related books
    foreach ($a->book->all as $b)
    {
        echo $b->id . '
    ';
        echo $b->name . '
    ';
        echo $b->description . '
    ';    
    }

    With the self referencing thing, I haven’t tried that yet but my first thought would be that you’d have to have a Model for employees who have a One to Many relationship with other employees (supervisor has multiple employees to supervise), and a Model for employees who have a One to One with other employees (employees have one supervisor).

    This would require you have a joining table of “employees_employees”.

    Supervisor Model

    <?php
    
    class Supervisor extends DataMapper
    {
        var $table = "employees";
    
        var $has_many = array("underling" => "employees");
    
        function Supervisor()
        {
            parent::DataMapper();
        }
    }

    Underling Model

    <?php
    
    class Underling extends DataMapper
    {
        var $table = "employees";
    
        var $has_one = array("supervisor" => "employees");
    
        function Underling()
        {
            parent::DataMapper();
        }
    }

    I haven’t tested that but I’m hoping it will work, lol 😊  Probably won’t since I suspect it will be looking for the same join key of (employee_id) and which you obviously can’t have two of.

    UPDATE

    Version 1.3 has been released!

    View the Change Log to see what’s changed.

    In short, the related objects has had an overhaul and automatic population of the related objects is now off by default (you now populate them in much the same way you do the normal objects) but can be turned back on easily if desired.  Validation has been improved, with the ability to properly label fields for use in error messages and you can now validate non-Database Table fields.

  • #49 / Sep 29, 2008 12:12pm

    GregX999

    39 posts

    Great work on getting 1.3 out Stensi!

    If “self-relational” joins they wouldn’t work due to both “id” fields being named the same, I think that’s a big issue. I think they are an important thing to be able to use (I certainly use them in many projects - such as assigning “related products” to products in an e-commerce site).

    Also,
    Following the supervisor/underling example, what would you do for an employee that has both a supervisor and underlings?
    Or in the case of a social networking site, how would you handle having “friends” (since each friend needs to point to the other)?

    Maybe the reference “id” names in the join table can be named after the model instead of the database table - so you’d have supervisor_id and underling_id instead of two employee_id fields??

    You explanation for wanting to use “book” (and “book->all”) instead of “books” makes total sense.

    Greg

  • #50 / Sep 29, 2008 6:29pm

    stensi

    109 posts

    Thanks Greg 😊

    Actually, now that you mention it you’re right!  It does use the model names to figure out the id fields in the joining tables. So the tables for the above self referencing example would be:

    employees
    id
    name

    employees_employees
    id
    supervisor_id
    underling_id

    The only problem, now that I’ve tested it, is that the JOIN query fails with the below error due to the JOIN having the employees table on both sides:

    Not unique table/alias: 'employees'
    
    SELECT employees.* FROM (`employees`) LEFT JOIN `employees_employees` ON employees.id = underling_id LEFT JOIN `employees` ON employees.id = supervisor_id WHERE employees.id = 1

    I’ll see if I can figure this out.  Hopefully it’s just a matter of having a different JOIN query if it looks like it’s going to be a self reference.  If I get this working, I’ll look at getting it to work for that 3rd type of employee scenario you mentioned.

    UPDATE

    Yep! It just needed the different type of JOIN query for a self reference 😊

    I’ll post the fixed version for that sometime today.

    I’m testing out the 3rd type of employee and it’s looking like I might need to change things around so it uses the pluralised version of the model for the table name, rather than just the table name, for joining tables.  So the joining table between Supervisors and Underlings would change from employees_employees to become:

    supervisors_underlings
    id
    supervisor_id
    underling_id

    This would make it easier when you have more than two types of employees self referencing, such as a Manager, who would then have a joining table for each type of employee it could join with, example:


    managers_underlings
    id
    manager_id
    underling_id

    managers_supervisors
    id
    manager_id
    supervisor_id

    If I go down this route, I can remove the need for developers having to specify the $table value for any of their models or in the $has_many and $has_one arrays, and I’ll just make DataMapper use CodeIgniter’s Inflector helper for automatically figuring out ALL of the singular/plural work.

    It’s smart enough to know that “Countries” is the plural of “Country” but unfortunately it’s not smart enough to know “People” is the plural of “Person”, and in those cases, this one particularly, you’d have to have “Person” as your Model and “Persons” as your Table.

  • #51 / Sep 30, 2008 2:12pm

    mintbridge

    2 posts

    Hi stensi,

    First of all, great work! This makes my life so much easier when it does to managing data!

    I have a problem though and its probably because im missing something obvious. I have 3 normal tables; courses, venues, categories and two joining tables categories_courses and courses_venues. Each course can have one venue and one category but the each category/venue can have many courses.

    Im trying to create a list of courses by category and then venue, something like:

    category_1
      venue_1
        course_1
        course_2
      venue_2
        course_3
    category_2
      venue_1
        course_4
      venue_2
        course_5
        course_6

    I thought i would be able to do it something like this :

    $c = new Category();
    $c->where('status', 0)->get();
    foreach ($categories->all as $category)
    {
        echo $category->name.'
    ';
        $v = new Venue();
        $v->where('status', 0)->get();
        foreach ($venues->all as $venue)
        {
            echo $venue->name.'
    ';
            $cs = new Course();
            $v->where('status', 0)->where('venue', $venue->id)->where('category', $category->id)->get();
            foreach ($courses->all as $course)
            {
             echo $course->name.'
    ';
            }
        }
    }

    But i cant get it to work!

    Any help would be great!
    Thanks in advance,
    Paul

  • #52 / Sep 30, 2008 6:10pm

    stensi

    109 posts

    Thanks mintbridge 😊

    Since you say that each Course can have one Venue as well as one Category, you can get to the Course directly from the Category if desired, rather than having to go through a Venue (but still being able to get a Course through a Venue).  If this is not what you’re wanting, you would of course remove the relationship between the Course and the Category, and just get to your Courses through the Venues.

    Can you to make sure you have your models are setup like the the following, to fit in with what you’ve told me about the relationships between each of them? I’ve made the assumption that a Venue has one Category but a Category has many Venues.

    Your Course model should have:

    $has_one = array("venue" => "venues", "category" => "categories");

    Your Venue model should have:

    $has_many = array("course" => "courses");
    $has_one = array("category" => "categories");

    Your Category model should have:

    $has_many = array("course" => "courses", "venue" => "venues");

    Obviously if they have other relationships you’d include those as well.

    Now, onto your code snippet.  Here’s how it should be done:

    $status = 0;
    
    // Get all categories with a particular status
    $c = new Category();
    $c->where('status', $status)->get();
    
    // Loop through each category
    foreach ($c->all as $category)
    {
        echo $category->name . '
    ';
    
        // Get all venues related to the category, with a particular status
        $category->venue->where('status', $status)->get();
    
        // Loop through each related venue
        foreach ($category->venue->all as $venue)
        {
            echo '  ' . $venue->name . '
    ';
    
            // Get all courses related to the venue, with a particular status
            $venue->course->where('status', $status)->get();
    
            // Loop through each related course
            foreach ($venue->course->all as $course)
            {
                echo '    ' . $course->name . '
    ';
            }
        }
    }
  • #53 / Oct 01, 2008 4:01am

    Heli

    16 posts

    Stensi, thanks for your library.
    But how can I set up linking the keys, if they are different from the “name of the table"_id?
    And how can I set the table for relations many to many?

  • #54 / Oct 01, 2008 8:38am

    stensi

    109 posts

    Your tables need to follow the rules set out in the Database Tables section of the DataMapper User Guide.  If your tables do not follow the rules then unfortunately, DataMapper will not be able to map your tables.

    As for setting up Many to Many relations, there are instructions in the Setting up Relationships section.  Basically, the models that have a Many to Many relationship with each other would both have a $has_many setting corresponding to one another.

  • #55 / Oct 01, 2008 9:28am

    Heli

    16 posts

    it would be good to have this opportunity to adjust.
    because is not always possible, such restrictions.IMHO.

  • #56 / Oct 01, 2008 10:36am

    Isuka

    24 posts

    Thanks for this great library. I’m trying it right now and it’s pretty cool 😊

    It’s maybe a stupid problem but I have boolean field in my table (a tinyint(1) called “is_published” where I store 1 or 0) and when I make a get() I can print 1 but not 0. The same thing appear when I want to update a row with “is_published” to 0, the field is not updated. Am I doing something wrong ?

    EDIT : I try to explain better 😛

    Here’s my model :

    class Member extends DataMapper 
    {
      var $validation = array(
            array(
                'field' => 'firstname',
                'label' => 'Prénom',
                'rules' => array('required', 'trim', 'xss_clean', 'max_length'=>255)
            ),
            array(
                'field' => 'lastname',
                'label' => 'Nom',
                'rules' => array('required', 'trim', 'max_length'=>255)
            ),
            array(
                'field' => 'curriculum',
                'label' => 'Curriculum',
                'rules' => array('trim')
            ),
            array(
                'field' => 'is_published',
                'label' => 'Etat de publication',
                'rules' => array('required', 'numeric')
            )
        );
    
        /**
         * Constructor
         *
         * Initialize DataMapper.
         */
        function Member()
        {
            parent::DataMapper();
        }  
    }

    Here’s my controller :

    function edit ($id = '')
      {
        $m = new Member();
        $m->get_where(array('id'=>(int)$id));
        echo $m->is_published; // print 1
        if(count($_POST) > 0) {
          echo $this->input->post('input[is_published]'); // print 0
          foreach($this->input->post('input') as $key=>$val) {
            $m->{$key} = $val;
          }
          echo $m->is_published.'
    '; // print 0
          if ($m->save()) {
            echo $m->is_published.'
    '; // print 0
          }
        }
      }

    Everything seems OK but if I look in my db, the field “is_published” is still at 1.

  • #57 / Oct 01, 2008 2:51pm

    Iverson

    153 posts

    First, let me say this is a great script. For those of us who are new to data mapping, there’s definitely a learning curve, but I’m quickly seeing how beneficial this can be. Could you explain a little about the validation? I understand it’s linked to CI’s Validation class, but I’m having a hard time figuring out how to retrieve errors. Say somebody doesn’t enter a valid email. Well the script knows that it isn’t valid, so it doesn’t complete. However I don’t see a way to grab the errors to see what’s wrong. I already know how to validate fields myself, but I’m hoping there’s a way I can tap into this script’s automatic validation when attempting to save().

  • #58 / Oct 01, 2008 6:17pm

    stensi

    109 posts

    Thanks for the compliments.

    One thing to take note of is one of the strange behaviours of PHP itself.  For example, if you ran this code:

    $one = TRUE;
    $zero = FALSE;
    
    echo "One: $one
    ";
    echo "Zero: $zero
    ";

    PHP would echo out:

    One: 1
    Zero:

    This is because a boolean value of FALSE (or 0) is not considered to be the actual number 0, so it will not echo 0.  Strangely enough, TRUE will still echo 1.  Try the above code and you’ll see.

    But, as you’re not yet showing error messages, the Database value might be staying as 1 because it is not validating successfully and saving.  To retrieve errors, there’s a few different ways to do it.

    // Create new Member
    $m = new Member();
    
    // Try to save the empty Member object 
    if ($m->save())
    {
        echo 'Member saved successfully.  ID: ' . $m->id . '
    ';
    }
    else
    {
        // 1. Echo all errors in the one string
        echo $m->error->string;
    
        // 2. Echo the error for each individual field
        echo $m->error->firstname . '
    ';
        echo $m->error->lastname . '
    ';
        echo $m->error->curriculum . '
    ';
        echo $m->error->is_published . '
    ';
    
        // 3. Loop through the error array and echo each error
        foreach ($m->error->all as $e)
        {
            echo $e . '
    ';
        }
    }

    If you modify your edit method to show the errors, you should be able to see why it’s not updating in the Database.  Use whichever way you’d prefer.

    For more information on using Error Messages, read the Validation section of the DataMapper User Guide (the part on Error Messages is towards the end of that section).

    Good luck!

    Looks like I answered both questions in one go 😉

  • #59 / Oct 01, 2008 10:57pm

    GregX999

    39 posts

    Wow dude, you rock! This is really looking very usable to me now - after these few changes.

    Making the join tables use the model’s name is perfect!

    I’ll just make DataMapper use CodeIgniter’s Inflector helper for automatically figuring out ALL of the singular/plural work.

    It’s smart enough to know that “Countries” is the plural of “Country” but unfortunately it’s not smart enough to know “People” is the plural of “Person”, and in those cases, this one particularly, you’d have to have “Person” as your Model and “Persons” as your Table.

    Could you make it an optional parameter somehow? So it’ll use the inflector unless you give it a plural to use?

    Greg

  • #60 / Oct 02, 2008 3:07am

    stensi

    109 posts

    Version 1.3.1 has been released!

    View the Change Log to see what’s changed.

    In short, the self referencing relationships are now fully supported.  I’ve included a complete example showing how it’s done, with some useful code gems amongst it.

    I’ve chosen not to go ahead with making CodeIgniter’s Inflector Helper automatically do all the singular/plural work, so things remain as normal in that regard (that is, it uses the Inflector Helper to figure out the table name only if you haven’t specified it, rather than it always figuring it out and ignoring your setting).

    I’ve included my improved version of the Inflector Helper, which handles a greater number of irregular nouns, to make the automated table naming better.

    I also made a mention of you in the Credits Greg, since you’ve been a great source of ideas and strong driving force in getting me to make DataMapper what it is now.  So thanks 😊

    Thanks to everyone else who’ve suggested things as well!

.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases