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]
  • #31 / Sep 22, 2008 9:29am

    GregX999

    39 posts

    In reply to the chicken dish and Joe’s Diner examples…

    That’s perfect, the ability to chain models is very handy! That would make life much easier.

    (ie: foreach($restaurant->menu->menu_item as $mi):)

    It’d be sweet if you could do something like this:

    $menuitem = new MenuItem();
    $menuitem->where(‘dish’, ‘chicken’)->where(‘menu->restaurant’, “Joe’s Diner”->get();

    instead of having to manually do a foreach loop.

    Can use “assign” relations before issuing a save call?
    Like this:

    $restaurant->menu->menu_item = ‘Chicken’; (or $restaurant->menu->menu_item->add(‘Chicken’);
    $restaurant->save();

    Otherwise you’d have to do it like this right?
    $restaurant->menu->menu_item->save($new_item); (having already created “$new_item” as a menu_item object

     

    Also, can you delete like this?
    $c->where(‘name’, ‘Australia’)->get()->delete();

     

    As far as requiring join tables for one-one and one-many relationships… I don’t think it’s valid to say “it was easier to program that way”, becuase now it’s more work for someone using the library to create more tables. As for not wanting tables to know about each other (and therefore not having foreign keys), it’s an interesting way of looking at things. Are there any reasons you can see that would make that a desirable thing? I can’t really think of any major pros/cons either way off the top of my head. It’s something I certainly could get used to if I started using it.

    One thing that could make life easier as far as managing lots of join tables is if you could manually set a join table name “prefix” (in database.php config file for example) so all join tables would appear together in an alphabetical list of table names (using “join_” or “_” for example).


    Thanks,
    Greg

  • #32 / Sep 22, 2008 11:30am

    GregX999

    39 posts

    It would also be nice to have a “dump” function (for debugging, logging, etc.) that would output all the fields and their values for a given object. (But only database fields, not other class variables.)

    Like this:

    $u = new User();
    $u->where(‘id’, 1)->get();
    $u->dump();

    would return:
    array(id=>1, name=>‘Greg’, username=>‘gregx999’, password => ‘********’)

    (‘password’ would be a “special” protected field that could be flagged that way in the model… thus outputting ‘*’ characters.)

    Does DataMapper know all the fields in a table? Or would they have to be declared them in the model (which would make this feature not as usable)?

    Greg

  • #33 / Sep 22, 2008 2:08pm

    GregX999

    39 posts

    I’m trying the following and am getting an error:

    In the User model:

    function signin($email, $password)
    {
        $this->email = $email;
        $this->password = $password;
        $this->validate()->get();
        ...
    }

    I call validate so the “encrypt” method is called on password. This is very similar to one of your examples. (My encrypt method doesn’t use a salt.)

    But I get the error:

    Message: Invalid argument supplied for foreach()
    Filename: models/datamapper.php
    Line Number: 505

    Greg

  • #34 / Sep 22, 2008 8:22pm

    stensi

    109 posts

    Wow, lots of questions there!  Thanks for your testing Greg.  You’ve pointed out a bug to me I hadn’t noticed.

    The get() method has an “if ($this->valid)” check where it shouldn’t.  I meant to only include that in the the save() method.  The fix is in version 1.2.1 which I’ve released.  Using that version will see your $this->validate()->get(); call work correctly.

    There’s several ways you can do a “signin” method for a user.  You don’t have to pass the email and password as you have, since you can assign the values directly to the user object before calling signin(), but there’s nothing wrong with doing it your way.  You could even set it up to allow doing both ways (this example is without a database stored salt, so I’d assume we’d have an _encrypt method that uses the salt setup in the CodeIgniter config file):

    Controller

    // Directly assigning values before calling signin()
    $u = new User();
    
    $u->email = $this->input->post('email');
    $u->password = $this->input->post('password');
    
    if ($u->signin())
    {
        // Signed in
    }
    
    // or
    
    // Supplying values to signin()
    $u = new User();
    
    if ($u->signin($this->input->post('email'), $this->input->post('password')))
    {
        // Signed in
    }

    Model

    function signin($email = '', $password = '')
    {
        if (!empty($email))
        {
            $this->email = $email;
        }
    
        if (!empty($password))
        {
            $this->password = $password;
        }
    
        // Cause validation rules to run (such as encrypt) and then try to get a matching record from the database
        $this->validate()->get();
    
        // If we have an ID, this user object is valid and fully populated (else its emptied)
        if (!empty($this->id))
        {
            // Create logged in session
            $this->session->set_userdata(array('id' => $this->id)); 
    
            return TRUE;
        }
        else
        {
            $this->error_message('signin', 'Email or password invalid');
    
            return FALSE;
        }
    }

    Yep, DataMapper knows all the fields in a table so you don’t need to declare them in your models.

    DataMapper currently has a private method that converts the objects field properties into an array (for internal use), but you can call it this way if you’re needing a dump like method:

    // Create your object and populate with all records
    $o = new Object();
    $o->get();
    
    // Convert first record into an array
    $array = $o->_to_array();

    Note that the resulting array doesn’t include any field properties with an empty value.

    I’ll have a look into having an optional db_prefix for join tables.


    Other things in the works

    zeratool has request I add in Database Transactions so that will be in the next version.  You can set a bool to turn on automatic transactions or you can leave that off and manually call the transaction methods yourself.

    I’m working on improving how you use and setup validation for the fields (as well as fields that don’t exist in the database, only on the front-end, such as “password confirmation”).

    I’m also looking at setting up the ability to allow DataMapper to automatically create your Database Tables (both normal and joining table) if they don’t already exist.  If you want to use this feature, it would require you to define your normal tables within each of your models.  I’ll be using DBForge for this.

  • #35 / Sep 23, 2008 6:03pm

    hugslife

    8 posts

    Hi, thanks for this. It’s really cool, and while I think I have a grasp using it for queries, I’m afraid I have to ask an ignorant question: What is the best way to pass results from the controller to the view?
    Or is the following meant to be called from the view?

    $u = new User;
    $u->get();
  • #36 / Sep 23, 2008 6:26pm

    GregX999

    39 posts

    I’m working on improving how you use and setup validation for the fields (as well as fields that don’t exist in the database, only on the front-end, such as “password confirmation”).

    Yes, please hurry up with this one!! 😊 I’m trying to do a password confirmation right now and am running into some difficulties.

    Greg

  • #37 / Sep 23, 2008 6:32pm

    stensi

    109 posts

    @hugslife: No problem. The usual way to pass data from the Controller to the View is like so:

    <?php
    class Blog extends Controller {
    
        function index()
        {
            $data['title'] = "My Title";
            $data['heading'] = "My Heading";
            $data['message'] = "My Message";
            
            $this->load->view('blogview', $data);
    
            // or you could build your "data" array like this:
    
            $data = array(
                'title' => 'My Title',
                'heading' => 'My Heading',
                'message' => 'My Message'
            );
    
            $this->load->view('blogview', $data);
        }
    }
    ?>

    For DataMapper objects, you can do it depending on your personal preference.  That is, you could give the View only the data you want from your DataMapper objects:

    // In Controller
    $u = new User();
    $u->get();
    
    $data['username'] = $u->username;
    $data['email'] = $u->email;
    
    $this->load->view('myview', $data);
    
    // In View
    echo $username;
    echo $email;

    Or put the objects in the array you send the view:

    // In Controller
    $u = new User();
    $u->get();
    
    $data['u'] = $u;
    
    $this->load->view('myview', $data);
    
    // In View
    echo $u->username;
    echo $u->email;
  • #38 / Sep 23, 2008 7:43pm

    hugslife

    8 posts

    awesome stensi, that did it! i appreciate your help 😊

  • #39 / Sep 25, 2008 12:26pm

    gusa

    47 posts

    great job, stensi!!!  😉
    i downloaded datamapper yesterday and it’s perfectly working under MSSQL Server.

    however, a single restriction prevents me from starting a new project with this release: the “chicken issue”. suppose my system handles 100.000 restaurants and each restaurant has 10.000 menu items (that’s unrealistic, but it ilustrates my problem). both approaches that you suggested are too much memory consuming.

    2:
    Ok, so the Restaurant has a One to One relationship with Menu, and Menu has a One to Many relationship with MenuItem.

    to simplify the problem, let’s assume that we have restaurants and items. so, Restaurant has a One to Many relationship with Items. if i want to list all the restaurants that serves chicken, it would be great if i could say something like:

    $r = new Restaurant();
    $r->items->where('name', 'chicken');
    $r->get();

    internally, datamapper would execute the following CI’s active record code:

    $this->db->select('*');
    $this->db->from('restaurants');
    $this->db->join('items_restaurants', 'items_restaurants.id=restaurants.id');
    $this->db->join('items', 'items.id=items_restaurants.id');
    $this->db->where('items.name', 'chicken');

    imho, this improvement is neccesary to insert datamapper into real world projects. i bet it will help us in the future.

  • #40 / Sep 25, 2008 12:39pm

    steelaz

    252 posts

    I agree with gusa, I had a similar problem with label->band->song setup and I ended up adding extra label->song relationship, so I could do $label->song->all instead of doing foreach loop.

    Improvement that gusa is proposing would be very welcome.

  • #41 / Sep 25, 2008 12:50pm

    GregX999

    39 posts

    Yes, that’s what I was trying to get at when I made the restaurant/menu examples.

    $r->items->where('name', 'chicken')->get();

    However, this makes it seem like you’re after all the items with chicken that the restaurant “$r” has. (The “where” is acting on the items of a particular restaurant.)

    Perhaps something like this to get all the restaurants that have an item with “chicken”:

    $r->have('items', array('name'=>'chicken'))->get();

    (“have” or “has” - would it matter?)

    Both of the examples above would be quite useful.

    Greg

  • #42 / Sep 25, 2008 7:25pm

    stensi

    109 posts

    Good suggestion.  This is something I noticed along the way but haven’t had time to look at much.

    The solution I’m favouring is to have the related object start off completely empty on first access and you’d have to populate it much like you do the normal objects.

    For example, let’s say we have an authors table with a One to Many relationship between the books table.  If we wanted to see what books an author has made:

    // Get author
    $a = new Author();
    $a->where('name', $name)->get();
    
    // If you wanted to access all books by this author, you'd now do it like this
    $a->book->get();
    
    foreach ($a->book->all as $b)
    {
        echo $b->name . '
    ';
    }
    
    // Or you could do it like this if you wanted to access a specific genre of books
    $a->book->where('genre', $genre)->get();
    
    foreach ($a->book->all as $b)
    {
        echo $b->name . '
    ';
    }

    So yeah, the related objects work like any DataMapper object, except its queries will always relate to its parent object.

    Thoughts?

    UPDATE

    I’ve got the above way working properly now. I’ve also changed the get() method to be chainable, so you can do it this way if you want:

    // Get author
    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->where('genre', $genre)->get()->all as $b)
    {
        echo $b->name . '
    ';
    }
  • #43 / Sep 26, 2008 4:45pm

    GregX999

    39 posts

    That’s pretty slick.

    Would this work to get a list of bookstores that sell an author’s books?

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->bookstore->where('city', $city)->get()->all as $bs)
    {
        echo $bs->name . '
    ';
    }

    And can I get a list of an author’s books available in a certain city like this?

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->bookstore->where('city', $city)->book->where('author', $a)->get()->all as $b)
    {
        echo $b->name . '
    ';
    }

    (I swear, I’m not trying to be a smart-ass!!)

    Greg

  • #44 / Sep 26, 2008 8:06pm

    stensi

    109 posts

    No problem 😊  Happy to have your questions since they’ve helped to tighten up and improve DataMapper.

    For the above question, unfortunately no, those wont work as is.

    Since related objects will be empty from now on, you’ll need to do a get() to populate it with at least one record before going into a deeper related object (otherwise there’s no parent record for the deeper related object to know what to relate to).

    So in your first snippet of code, you need a get() after the book, for example, I’ll limit to getting 10 books:

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->get(10)->bookstore->where('city', $city)->get()->all as $bs)
    {
        echo $bs->name . '
    ';
    }

    Now, the above would only look at the bookstores for the first book, since there’s nothing above to tell us to loop through all the books, and look at all the bookstores for each of those books.  That’s an important thing to take note of.  There’s no simple way I know of that would allow you to foreach through 1st level related objects and 2nd level (or more) related objects in the one foreach.  I don’t think that’s do-able.

    Also note that related objects are only aware of their parent object and no higher (they’re not aware of their parents parent object etc).

    If you wanted to look at all books the author has, and all the bookstores they’re in, you’d have to do:

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->get()->all) as $b)
    {
        foreach ($b->bookstore->where('city', $city)->get()->all as $bs)
        {
            echo $bs->name . '
    ';
        }
    }

    or you could do it this way:

    $a = new Author();
    $a->where('name', $name)->get();
    
    $a->book->get();
    
    foreach ($a->book->all) as $b)
    {
        $b->bookstore->where('city', $city)->get();
    
        foreach ($b->bookstore->all as $bs)
        {
            echo $bs->name . '
    ';
        }
    }

    Onto your second snippet, that wont work for the reasons stated above, but also, because you’re missing a get() call after your first where call and you’re using the original author object as part of a where clause (not possible - you would need to pass the authors name instead).

    Am I right in seeing you’re almost doing a full relationship loop, from the author through his books, the bookstores they belong to, and then the books in the bookstores that belong to the author?

    Not that you’d ever really want to do this 😉 but here’s how it can be done:

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->book->get()->all as $b)
    {
        foreach ($b->bookstore->where('city', $city)->get()->all as $bs)
        {
            foreach ($bs->book->where('author', $a->name)->get()->all as $book)
            {
                echo $book->name . '
    ';
            }
        }
    }

    or

    $a = new Author();
    $a->where('name', $name)->get();
    
    $a->book->get();
    
    foreach ($a->book->all as $b)
    {
        $b->bookstore->where('city', $city)->get();
    
        foreach ($b->bookstore->all as $bs)
        {
            $bs->book->where('author', $a->name)->get();
    
            foreach ($bs->book->all as $book)
            {
                echo $book->name . '
    ';
            }
        }
    }
  • #45 / Sep 27, 2008 10:54am

    GregX999

    39 posts

    Could you get rid of the requirement to call “get” on a relationship by checking to see if that relationship has been populated, and if not, by populating it?

    So you could do this:

    $a = new Author();
    $a->where('name', $name)->get();
    
    foreach ($a->books as $b){
    ...
    }

    Is there a way that $a could know that “books” is empty so it should perform a “get” on it. Or maybe if $a just knows it “has many” books it can just always do the “get” if that function already handles caching.

    Then I would think you COULD chain relationships (as long as each relationship was a “has one” - except for the last on in the chain which could be anything):

    // When viewing an ad, gets other ads posted by the same user:
    $a = new Ad();
    $a->where('item', $item)->get();
    
    foreach ($a->user->ads as $ua){
    ...
    }

    Also, can you make custom methods that return an object then use them in a chain?

    // In the Author class:
    function most_popular_book()
    {
    return $b // $b is an object of class Book
    }
    
    // In a controller or view:
    $a = new Author;
    echo $a->most_popular_book->publish_date;

    Greg

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

ExpressionEngine News!

#eecms, #events, #releases