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]
  • #76 / Oct 06, 2008 5:43am

    stensi

    109 posts

    @Boyz26: It’s hard to know what’s wrong without any real context to your issue.  Could you show me how you’ve setup your Person and Book models? If you have no relationships saved then no relationships will be returned by DataMapper so you must have something setup wrong somewhere 😕

  • #77 / Oct 06, 2008 8:34am

    Isuka

    24 posts

    I’m wondering how I can handle validation in the case I have a “has one” relation.

    I have something like this :

    Client model :

    class Client extends DataMapper 
    {
      var $table = "clients";
      var $has_one = array("category" => "categories");
      
      var $validation = array(
        array(
                'field' => 'name',
                'label' => 'Nom',
                'rules' => array('required', 'trim', 'xss_clean', 'max_length'=>255)
            ),
            array(
                'field' => 'website',
                'label' => 'Site web',
                'rules' => array('trim', 'prep_url', 'xss_clean', 'max_length'=>255)
            )
        );
    
        function Client()
        {
            parent::DataMapper();
        }
    
    }

    Category model :

    class Category extends DataMapper 
    {
      var $table = "categories";
      var $has_many = array("client" => "clients");
      
      var $validation = array(
        array(
                'field' => 'title',
                'label' => 'Titre',
                'rules' => array('required', 'trim', 'xss_clean', 'max_length'=>255)
            )
        );
    
        function Category()
        {
            parent::DataMapper();
        }
        
        function getForDropdown()
        {
          $o = new Category();
        $o->select('id, title');
        $o->get();
        $array = array(''=>'-----');
        foreach($o->all as $row) {
          $array[$row->id] = $row->title;
        }
        return $array;
        }
    }

    In the case I want insert a new client, I need the name, eventually the website and the category the client belong to. The category is required.
    In my view form, I set up an input for the name and the website, and a dropdown menu for the category. How can I make the dropdrown menu required ? Is this handle by DataMapper ?

  • #78 / Oct 06, 2008 9:49am

    stensi

    109 posts

    Hmm, well since you’re requiring the Category ID when saving a new Client, I’d put an extra validation rule in the Client model.

    In Client model

    array(
        'field' => 'category_id',
        'label' => 'Category',
        'rules' => array('required', 'numeric')
    )

    I’m assuming the drop-down list with your Categories has the Category ID’s as the keys, and the Category Titles as the values.  Also, that your form post sends the choice as “category_id”.

    When you save the new Client, you’ll need to save the relationship separately.  Example:

    // Create client object
    $c = new Client();
    
    // Populate client object with user input
    $c->name = $this->input->post('name');
    $c->website = $this->input->post('website');
    $c->category_id = $this->input->post('category_id');
    
    if ($c->save())
    {
        // Validation requirements passed (included a Category being chosen) and Client now created
    
        // Get chosen category
        $cat = new Category();
        $cat->where('id', $c->category_id)->get();
    
        // Save relation
        if ($c->save($cat))
        {
            // Client now related to category
        }
    }
    else
    {
        echo $c->error->string;
    }

    I’ll be releasing a new version soon that handles the non-Database Table field validation rules a little differently, in that it will have a separate setting so the developer can specify whether the rules for those are run both when creating and updating a record, or just when creating.

  • #79 / Oct 06, 2008 10:31am

    Isuka

    24 posts

    Arf i didn’t think about adding a fake rule to client model 😝

    My controller part is pretty like your exemple so it’s work fine.
    Is it really not possible to save a relation on an non-existing object ?

    It would be really great if I can have a controller like that :

    function edit ($id = '')
    {
      $c = new Client();
      if($id) {
        $c->get_where(array('id'=>(int)$id));
      }
      
      if(count($_POST)) {
        foreach($this->input->post('input') as $key=>$val) {
          $c->{$key} = $val;
        }
        $cat = new Category();
        $cat->where('id', $this->input->post('input[category_id]'))->get();
        if ($c->save($cat)) {
          echo 'saved';
        }
      }
    
      $data = array(
        'client'=>$c,
        'categories'=>$cat->getForDropdown()
      );
      
      $this->view->set($data);
      $this->view->part('content', 'edit_client.php');
      $this->view->load('layout');
    }

    where the save() method handle the relation on his own.

    Also a great feature would be to have the ability to delete a row without the need to make a get() before

    // Exemple : delete a user by his ID
    $u = new User();
    $u->where('id', 1)->delete();

    (Sorry for my bad (very bad) english)

  • #80 / Oct 06, 2008 2:15pm

    Boyz26

    28 posts

    Hi I am a little frustrated now so if anyone can help that would be great..

    Whenever I do this,

    function date2()
        {
            $person = new Person();
            $person->where('name','Andrew')->get();
    
            $person->book->get();
            
            foreach($person->book->all as $b):
                echo $b->author;
            endforeach;        
        }

    It simply returns all entries from Book. I have not saved any relationships yet, so by right it should not display anything.
    Can someone tell me what I did wrong here?

    Thank you.

    My models are as below:

    //models/person.php
    <?php
    class Person extends DataMapper {
    
        var $has_many = array("set" => "sets");
        var $table = 'people'; 
        
        function Person()
        {
            parent::DataMapper();
        }
        
        
    }
    ?>
    //models/book.php
    <?php
    class Book extends DataMapper {
    
        var $table = 'Books'; 
        var $has_many = array("person" => "people");
        
        function Set()
        {
            parent::DataMapper();
        }
        
        
    }
    ?>

    Can you tell me what is wrong with it? Thanks again!

  • #81 / Oct 06, 2008 5:51pm

    stensi

    109 posts

    @Isuka: Yep, at the moment the save() method, if not passed any parameters, will insert or update a record.  If it’s passed a parameter (object) then it will insert or update a relationship record.

    The worry I have about setting it up to do both in one go, is if you’re doing an update to a record, you probably don’t want to re-save all the existing relationships that haven’t changed.  I could try setting it up to save relationships only on new records that have the relationship ID specified but then that means the behavior is slightly different between insert and update.  I’ll see what I can come up with.

    On being able to delete in the way you mentioned, yep, you’re right that it would be much easier.  I’ll see how much of a change would be required for that type of functionality.

    @Boyz26: When setting up a relationship between models, you need to specify the relationship in both models, not just one of them.  It’s looking like you want a Many to Many relationship between Person and Book so modify the Person’s $has_many var to be like so:

    var $has_many = array("set" => "sets", "book" => "books");

    Also, I expect in your Book model that you should set the $table to be all in lowercase ( “books” ).  But, it’s not necessary to specify the $table if the plural of the singular model name is simply a matter of adding the letter “s” on the end (like in the case of book to books).

    Give those changes a try and let me know how it goes.

    UPDATE

    Ah, I just noticed that the Constructor of your Book model is “Set”.  The Constructor must be named the same as the class name (Book).  You should change the Book model to be:

    <?php
    class Book extends DataMapper {
    
        var $has_many = array("person" => "people");
        
        function Book()
        {
            parent::DataMapper();
        }    
    }

    And your Person model to be:

    <?php
    class Person extends DataMapper {
    
        var $table = 'people';
    
        var $has_many = array("book" => "books");
        
        function Person()
        {
            parent::DataMapper();
        }
    }

    You should have the following tables:

    people
    books
    books_people

  • #82 / Oct 06, 2008 6:25pm

    Paul Apostol

    43 posts

    Excellent work and thank you for this very useful library.
    Because I’ve tried it on my application which has 60 tables (with normalization will be more) I got some minor problems and I discovered some things (correct me if I’m wrong):
    1. The models must be in the same folder with datamapper.
    2. The tables can’t have a prefix
    3. The ID must be named ‘id’ for the tables and for joining table ‘id_tablename’ (sg)

    I’ll make a lot of models and I prefer to put them in different folders. A solution (maybe) is to have a datamapper class in each folder and to load it in the controller and not in autoloader.

    Having so many tables I prefixed them with a keyword (prefix_tablename) and it’s a problem with joining tables. Maybe a solution is to have a variable which set a prefix for each model. Also, for the related tables it search for plurals, even when it’s not applied.

    The last problem is not so important. I prefer a naming convention like ‘idTablename’ for index and ‘tablename_idTablename’ for joining tables (redundancy?). With such normalization maybe is better (for me) to use same name of the ID in the joining tables like in the normal tables.

    All the best.


    PS: I forgot, sometimes I need to extract users related to books, other times books related to users. What is the naming convention for the joining tables?

  • #83 / Oct 06, 2008 6:57pm

    Boyz26

    28 posts

    Yup I made sure all of those are the as you said. It still gave me all the authors.

    I really appreciate your willingness to help.

    EDIT:
    I started again from scratch and just made the very simple program:

    In the controller i have:

    function index()
        {
            $student = new Student();
            $student->where('name','Andrew')->get();
    
            $book = new Book();
            $book->where('id',1)->get();
            
            $student->save($book);
            
            foreach($student->book->get()->all as $b):
                echo $b->author.'
    ';
            endforeach;        
        }

    models/student.php

    <?php
    class Student extends DataMapper {
    
        var $has_many = array("book" => "books");
        
        function Student()
        {
            parent::DataMapper();
        }
        
        
    }
    ?>

    models/book.php

    <?php
    class Book extends DataMapper {
    
        var $has_many = array("student" => "students");
        
        function Book()
        {
            parent::DataMapper();
        }
        
        
    }
    ?>

    The database tables are
    students: id, name
    books: id, title, author
    books_students: id, book_id, student_id

    I populated students and books, and ran index.php, but it still gave me all the authors.

    Is there something that I am doing wrong?

  • #84 / Oct 06, 2008 7:32pm

    Paul Apostol

    43 posts

    @Boyz26: I had the same problem and I fixed like I said. Maybe the joining table is not named as it should

    And some changes for the previous point (changes proposals), for the DataMapper class:
    1.

    var $table_prefix;

    2. in DataMapper function

    // Determine table name
    if (empty($this->table))
    {
        $this->table = plural(get_class($this));
    }
    //adding table prefix - added by me
    if (!empty($this->table_prefix))
    {
        $this->table = $this->table_prefix . $this->table;
    }
    // end addition

    3. changes in _related function

    function _related($table_in, $model, $id)
        {
            // No related items
            if (empty($table_in) || empty($model) || empty($id))
            {
                return;
            }
            if(!is_array($table_in)){
                $table = plural($table_in);
    
                    
            } else {
                $table = $table_in['prefix'] . $table_in['table'];
                    
            }
            if (empty($model))
            {
                $model = singular($table);
            }
            $this->model = strtolower($this->model);
    
            // Determine relationship table name
            $relationship_table = $this->_get_relationship_table($table, $model);
    
            // Retrieve related records
            if (empty($this->db->ar_select))
            {
                $this->db->select($this->table . '.*');
            }
            
            
            if(is_array($table_in) && !empty($table_in['id'])){
                if ($this->table == $table)
                {
                    $this->db->from($this->table);
                    $this->db->join($relationship_table, $table . '.'.$table_in['id'].' = ' . $this->model . '_'.$table_in['id'], 'left');
                    $this->db->where($relationship_table . '.' . $model . '_'.$table_in['id'].' = ' . $id);
                }
                else
                {
                    $this->db->from($this->table);
                    $this->db->join($relationship_table, $this->table . '.'.$table_in['id'].' = ' . $this->model . '_'.$table_in['id'], 'left');
                    $this->db->join($table, $table . '.'.$table_in['id'].' = ' . $model . '_'.$table_in['id'], 'left');
                    $this->db->where($table . '.'.$table_in['id'].' = ' . $id);
                }
            } else {
                // Check if self referencing
                if ($this->table == $table)
                {
                    $this->db->from($this->table);
                    $this->db->join($relationship_table, $table . '.id = ' . $this->model . '_id', 'left');
                    $this->db->where($relationship_table . '.' . $model . '_id = ' . $id);
                }
                else
                {
                    $this->db->from($this->table);
                    $this->db->join($relationship_table, $this->table . '.id = ' . $this->model . '_id', 'left');
                    $this->db->join($table, $table . '.id = ' . $model . '_id', 'left');
                    $this->db->where($table . '.id = ' . $id);
                }
            }
            $query = $this->db->get();
    
            $this->model = ucfirst($this->model);
    
            // Clear this object to make way for new data
            $this->_clear(TRUE);
    
            if ($query->num_rows() > 0)
            {
                // Populate all with records as objects
                $this->all = $this->_to_object($query->result(), $this->model, $this->fields);
    
                // Populate this object with values from first record
                foreach ($query->row() as $key => $value)
                {
                    $this->{$key} = $value;
                }
            }
    
        }

    In the model you can declare like this:

    var $table = "tablename";
        var $table_prefix = "prefix";
        var $has_many = array('modelname' => Array('table'=>'relatedtablename', 'prefix'=>'joiningtableprefix', 'id'=> 'tableid'));

    PS: no, it’s not good :( I have to test it more and add some other changes. Sorry, tomorrow I’ll make the corrections if looks interesting for you.

  • #85 / Oct 06, 2008 7:47pm

    stensi

    109 posts

    @Paul Apostol: Wow, 60 tables!  I can see why you prefer to group them into sub-directories, lol.  It wouldn’t be too difficult for me to modify the __autoload function to search sub-directories within the models folder, if the class isn’t found in the same folder as DataMapper.  I’ll add that to the Road Map.

    I’m looking at including a table prefix, which will apply to both the normal and joining tables.  Do you have different prefix’s for your tables?  Using the prefix as a way to group them?  I guess this could be accommodated when I introduce prefixes, but in the case of having different prefixes for different models, you would end up having to specify that setting in each of the models, rather than just the main DataMapper model.

    The id does have to be “id” and to clarify for the joining tables, it’s the singular of the table followed by “_id”.  I don’t plan on changing this naming convention.

    When setting up a relationship, you need to specify the $has_many or $has_one in both models.  Lets say we have Author, Book and Genre objects, with a Many to Many relationship between the Author and Book and a One to Many between the Book and Genre.  You would have the following tables:

    Normal
    authors
    books
    genres

    Joining
    authors_books
    books_genres

    And the models setup like so:

    Author model

    <?php
    
    class Author extends DataMapper
    {
        $has_many = array("book" => "books");
    
        class Author()
        {
            parent::DataMapper();
        }
    }

    Book model

    <?php
    
    class Book extends DataMapper
    {
        $has_many = array("author" => "authors");
        $has_one = array("genre" => "genres");
    
        class Book()
        {
            parent::DataMapper();
        }
    }

    Genre model

    <?php
    
    class Genre extends DataMapper
    {
        $has_many = array("book" => "books");
    
        class Genre()
        {
            parent::DataMapper();
        }
    }

    With that done, you can access the authors from a book, and the books from an author, and onto the genres from there etc.

    $a = new Author();
    $a->get();
    
    echo $a->name . ' has written these books:
    ';
    
    $a->book->get();
    
    foreach ($a->book->all as $b)
    {
        $b->genre->get();
    
        echo $b->title . ' (' . $b->genre->name . ')
    ';
    }

    Going the reverse way:

    $g = new Genre();
    $g->get();
    
    echo 'The following books are in the ' . $g->name . ' genre:
    ';
    
    $g->book->get();
    
    foreach ($g->book->all as $b)
    {
        echo $b->title . ' was written by:
    ';
    
        $b->author->get();
    
        foreach ($b->author->all as $a)
        {
            echo $a->name . '
    ';
        }
    }

    @Boyz26: I’ll setup the same stuff you’ve mentioned and see what happens for me.  Stay tuned.

  • #86 / Oct 06, 2008 7:52pm

    Boyz26

    28 posts

    Alright. Thanks!

  • #87 / Oct 06, 2008 8:24pm

    stensi

    109 posts

    Ok, when I ran it without any relationships saved, no relationships were returned (ie, no authors echo’d).  After saving one relationship, I got that one relationship back (not all of them like you were getting).

    Can you make sure you have the latest version of datamapper.php (1.3.3) in your models folder?

    I’ve attached a zip that has the files I tested with, as well as a SQL script you can run to recreate the tables I used (with some pre-populated data).

    Note
    Running the SQL script in the zip will DROP the existing books, students, and books_students tables so backup any data in those you want to keep before running the SQL script. Also, replace all occurrances of `datamapper` in the SQL script with the name of your Database so it will work.

  • #88 / Oct 06, 2008 8:29pm

    Boyz26

    28 posts

    Wow.. I’ll check it out. But thanks a lot.

  • #89 / Oct 06, 2008 8:39pm

    Boyz26

    28 posts

    I tested your file, but it is still the same. So I guess something is wrong with my computer..
    I have attached the whole file. I think the only difference that could be, is the autoload part:

    $autoload['libraries'] = array('database');
    $autoload['helper'] = array('url', 'form'); 
    $autoload['model'] = array('datamapper','student','book');


    Other than that, I really don’t know why it is not working for me.

    (I got this from the examples controller)

    Showing the students related books.
    
    alpha
    id: 1
    title: titleOne
    author: authorOne
    id: 2
    title: titleTwo
    author: authorTwo
    id: 3
    title: titleThree
    author: authorThree
    id: 4
    title: titleFour
    author: authorFour
  • #90 / Oct 06, 2008 8:43pm

    stensi

    109 posts

    Ah, that might be it.  You shouldn’t autoload your models that extend DataMapper as it autoloads them all for you.  Change it to:

    $autoload['libraries'] = array('database');
    $autoload['helper'] = array('url', 'form');
    $autoload['model'] = array('datamapper');

    Note that DataMapper is written for PHP5 so if you’re running it in PHP4, it might not work correctly.

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

ExpressionEngine News!

#eecms, #events, #releases