CodeIgniter Forums
DataMapper 1.6.0 - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Archived Discussions (https://forum.codeigniter.com/forumdisplay.php?fid=20)
+--- Forum: Archived Libraries & Helpers (https://forum.codeigniter.com/forumdisplay.php?fid=22)
+--- Thread: DataMapper 1.6.0 (/showthread.php?tid=11358)



DataMapper 1.6.0 - El Forum - 10-15-2008

[eluser]OverZealous[/eluser]
@Boyz26 - You are very welcome!

[quote author="commandercool" date="1224137409"]
should that work then for photos that have no album? or only exists in a profile?.. will many relationships on an item like a photo create performance issues?[/quote]

This should work fine. I'm doing almost the exact same thing with notes. My notes can be attached to multiple items, but are assigned to only one at a time (unlike how you have multiple items sharing a photo).

Thist is pretty simple to model, you just need separate tables for each relationship.

[quote author="commandercool" date="1224137409"]
Also, for certain items, that say has one file, i can specify that in the has_one array, but i'm getting lost when trying to store a label for that (or config data), when producing a form for instance.. because the photos are generic but i want to be able to give it a specific purpose or name, depending on the item it's attached to. I'm attempting to put that information in the model validation array, but i think i'm just adding too much to the problem.[/quote]

Well, I don't know how to store a label based on which item is selected, but you can easily determine which type of owner a photo has. Sadly, it requires one DB check for each type, but I'm currently just doing a lookup on each association, and then checking to see if it found anything — ie: if(empty($this->user->id)). If it's empty, you move on to the next type. Ugly, but functional.

If you are going the other way ($user->photo), you can check the related field from within your photo object. Put together, it might look like this:
Code:
public static $type_labels = array(
    'user' => 'User Photo',
    'group' => 'Group Photo',
    'userprofile' => 'User Photo'
);

function get_owner_type() {
    $type = NULL;
    if(isset($this->related) && isset($this->related['model']) {
        $type = $this->related['model'];
    } else {
        // check each model type
        foreach(self::$type_labels $k => $t) {
            $this->{$k}->get();
            if( ! empty($this->{$k}->id) ) {
                $type = $k;
                break;
            }
        }
    }
    return $type;
}

function get_type_label($type => NULL) {
    if(is_null($type)) {
        $type = $this->get_owner_type();
    }
    if(array_key_exists($type, self::$type_labels)) {
        return self::$type_labels[$type];
    } else {
        return 'ERROR';
    }
}

Hopefully that all makes sense. I just typed it up here in the browser, so I don't know how many errors it contains Tongue

SMALL UPDATE: I tweaked my example to use a static array, instead of a class member array. This prevents the array from being loaded into memory for each photo. If using PHP4, you have to remove the public I believe.


DataMapper 1.6.0 - El Forum - 10-15-2008

[eluser]ntheorist[/eluser]
@OverZealous - cool.. thx for clarifying on the performance issues

I haven't really looked too closely at DM's internal code, but i think i will take a crack at it... although i believe now the route i want to go involves creating interfaces for each kind of datamap i want to use. Each with a set of common properties, as well as its own unique functions based on its role.

Code:
$content->get_label();
$content->get_type();
$content->get_add_form();
// or like your function - haven't tested yet
$content->get_related_labels();

// additionally, for files
$file->set_config();
$file->upload_and_save();
$file->lock();

//etc

i mean you understand i'm attempting to create a cms in which you can dynamically define what an object consists of and how it relates to other objects.. and have an interface within them, allowing those objects to be categorized, searchable, listable, with meta data about their relationships stored and accessible. I dunno yet if i want to start creating abstract classes or go thru extends.. this is my first true app so i'm learning as i go. Php5 does seem the way to go however, as it's object model seems to exceed php4 quite a ways, and hosting is almost never a question nowadays.

for instance, for the datatypes i am using (sorta like data archtypes), (string, date, currency, text, photo..) i'm thinking i need to make a class for each, with a common interface but varying methods, rules, etc. These then would be used directly by a datamap class to format its data & relationships for any purpose. So something like
Code:
foreach($this->fields as $field) {
     $field->value = $this->{$field};
     $field->list_format();
}

// a field of type 'email' with the value '[email protected]' would produce
<a href="mailto:[email protected]">[email protected]</a>

// a field of type 'currency' with the value '1452.25' would produce
$1,452.25 USD
A kinda off topic problem i'm facing here however, is how to store multiple objects within an object for iteration and method chaining (arrays and serialization seem out of the question) - any advice here?

anyway i will try to dive more into DM and try to use its strengths to maximum effect, it seems like one of those things where a little extra work goes a long way. thx for pointers and expertise, too!

CC


DataMapper 1.6.0 - El Forum - 10-15-2008

[eluser]OverZealous[/eluser]
It's funny you mention the metadata. I'm working on a similar concept. Mine, however, allows fields to define what type of data they are - I'm using the existing validation array for this.

I'm not going to bother trying to explain everything just yet, but to give you an idea how I'm handling the rendering:

I wanted to be able to output &lt;?= $object->field safely from within my views. I didn't want to have a whole bunch of escapes around every object. Obviously, if I did it that way, I could never access the un-escaped value.

What I did was create a special Html_viewer Library. This class uses the __get($name) override to dynamically select the field from the original class, but run it through filters, convert it, or whatever needs to be done.

To assign this class, I added this to my DataMapperExt:
Code:
__get($name) {
    // lazily create the Html_viewer
    if($name == 'v') {
        if(!isset($this->v)) {
            $this->load->library('html_viewer');
            $this->v = new Html_viewer($this);
        }
        return $this->v;
    } else {
        // call DataMapper's __get
        return $parent::__get($name);
    }
}

What's slick about this is that it doesn't get loaded unless it is needed. Now, to output the cleaned up element, I just call $object->v->field.

There's a ton more functionality I added, and I also created an Html_editor that works the same way.

Hopefully this will give you some inspiration for how to access this dynamically.

As for your chaining issue, I'm not sure what you are asking.


DataMapper 1.6.0 - El Forum - 10-16-2008

[eluser]ntheorist[/eluser]
um, anyone know what happened to the recent versions of DM?

if you go to the changelog or download page, it's reverted to version 1.3.4 somehow..

CC


DataMapper 1.6.0 - El Forum - 10-16-2008

[eluser]BaRzO[/eluser]
Yesterday the stensi.com was not responding
see attach you can get the latest version of DataMapper 1.4.5


DataMapper 1.6.0 - El Forum - 10-17-2008

[eluser]Dready[/eluser]
@OverZealous.com : your "v" thing really rocks ! It's a really good idea for implementing features on top of a base class.


DataMapper 1.6.0 - El Forum - 10-17-2008

[eluser]OverZealous[/eluser]
Thanks! I'm working on integrating DoJo into the editing side. That will allow views to simply call $object->e->name, and get a complete editing line, properly formatted, with id, name, and client-side validation. It also allows $object->e->name->line($label), and prints the appropriate label. What's really cool is that line can be replaced by, say, html or plain for multiline editing, or just about any other form-input type.

Once I get where a certain level appears to be functioning, I'll see about posting the whole shebang up.


DataMapper 1.6.0 - El Forum - 10-19-2008

[eluser]ntheorist[/eluser]
okay, so i've been attempting to hack datamapper's code to fit it to what i've been trying to do, and it seems so far i'm having some success.

While DM works great for organizing tables and data and providing a great object model for running queries, plus being a relatively tiny library (almost 1/5 the size of IgnitedRecord, which I've also been looking at), it doesn't yet seem to handle the relations beyond loading the model of a related item. When a datamap's related items are populated, it requires another entire query (double join) to load it, and if you're listing hundreds of rows in a query, each with possibly several relations all requiring separate queries to load their data, and you again multiply that by perhaps many users running these queries at the same time.. it worried me about performance and scalability.

So here's what i'm working on to improve performance in terms of relations. Keep in mind too, this is a work in progress.. I'm making edits and adding functions/properties to datamapper to allow this to work, and i still have to address a few things, such as method chaining, etc.. Right now i'm focused on manipulating the active record for get() calls.

When running a query with an item i know has related items, before calling get(), i call this function (which i'm just writing directly into datamapper to provide for all models)

Code:
function join_related()
{
    // Don't run a join_related directly on a related model (yet..)
    if ( ! empty($this->related))
    {
        return FALSE;
    }
    
    // Prevent Re-join if join has already been called
    if ( $this->tables_joined === TRUE)
    {
        return TRUE;
    }
    
    // Check if no select statements have been made yet, if not, the table
    
    if(empty($this->db->ar_select))
    {
        $this->db->select($this->table.'.*');
    }            
    
    // Join has_one models with their id and specified value(s)
    
    foreach($this->has_one as $model)
    {
        $model_class = ucfirst($model);
        
        // Instantiate new model
        $ho_model = new $model_class();
        
        $ho_table = $ho_model->table;
        $ho_value = empty($ho_model->display_value) ? 'id' : $ho_model->display_value;
        $ho_relationship_table = $this->_get_relationship_table($this->prefix, $ho_table, $model);
        
        // Select values as aliased columns
        $this->db->select($ho_table.'.id AS '.$model.'_id');
        $this->db->select($ho_table.'.'.$ho_value.' AS '.$model.'_value');
        
        // Add aliased columns to datamap field list
        $this->fields[] = $model.'_id';
        $this->fields[] = $model.'_value';
        
        // Double join relation table
        $this->db->join($ho_relationship_table, $ho_relationship_table.'.'.$this->model.'_id = '.$this->table.'.id','left');
        $this->db->join($ho_table, $ho_table.'.id = '.$ho_relationship_table.'.'.$model.'_id','left');
    
    }
    
    // Join has_many models with their counts for this model
    
    foreach($this->has_many as $model)
    {
        $model_class = ucfirst($model);
        
        $hm_model = new $model_class();
        $hm_table = $hm_model->table;
        $hm_relationship_table = $this->_get_relationship_table($this->prefix, $hm_table, $model);
        
        $this->db->select('(SELECT count(*) FROM (`'.$hm_relationship_table.'`) WHERE `'.$this->model.'_id` = '.$this->table.'.id) AS '.$model.'_count');
        
        $this->fields[] = $model.'_count';
    }
    
    $this->tables_joined = TRUE;

    return TRUE;

}

The basic idea here is that if you have a 'has_one' model, meaning there's one of those models for each of the current one you're using, you are asking it to include the alias columns 'model_id' and 'model_value', in every row of the result. The 'model_value' field is determined in a model's definition. For instance, a 'User' has_one 'Userclass' (Admin, Manager, Member, etc..), so when joining a userclass to a user, to set the data that populates in 'userclass_value' to the field 'name' of the userclass, its simply stated in the userclass model:

Code:
class Userclass extends DataMapper {

    var $table = 'userclasses';
    var $controller = 'userclasses';
    var $model = 'userclass';
    
    var $has_many = array('user');
    
    var $display_value = 'name';
    
    var $validation = array(
        array(
            'field' => 'name',
            'label' => 'Class Name',
            'rules' => array('required', 'trim', 'unique', 'alpha', 'min_length' => 3)
        )
    );
        

    function Userclass()
    {
        parent::DataMapper();
    }
}

if no $display_value is defined, it defaults to id.

thus, now i can call in my controller

Code:
$user = new User();
$user->join_related();
$user->get();

this produces only one query:

SELECT users.*, userclasses.id AS userclass_id, userclasses.name AS userclass_value FROM (`users`) LEFT JOIN `userclasses_users` ON userclasses_users.user_id = users.id LEFT JOIN `userclasses` ON userclasses.id

and i can continue in my controller:

Code:
foreach($user->all as $u) {
    echo $u->username . ' is a ' . $u->userclass_value . ' which is userclass ID #'.$u->userclass_id.br();
}

This is also handy for generating forms where you may want to have a drop down to select a userclass, and you want to set it's selected value to 'userclass_id'. Also, it works nicely for creating links in the loop, ie:
Code:
$link = anchor('userclasses/view/'.$u->userclass_id',$u->userclass_value);

and yes, multiple has_one and has_many models can be all combined into a single query. I'll probably have to implement table alias's as well, to allow for multiple self-joins if needed

more soon..


DataMapper 1.6.0 - El Forum - 10-19-2008

[eluser]ntheorist[/eluser]
As for 'has_many' fields, instead of joining one ID and one value column, it uses a subselect to return a count, with the field 'model_count' - (i'm soon implementing a subselect where clause function, so the count field will be based on specified conditions - or even multiple count fields can be used )

Code:
$userclass = new Userclass(); // $has_many = array('user);
$userclass->join_related();
$userclass->get();

// Produces SELECT userclasses.*, (SELECT count(*) FROM `users_userclasses` WHERE `userclass_id` = userclasses.id) AS user_count

foreach($userclass->all as $uc)
{
   echo $uc->name .' has '.$uc->user_count.' Users.';
}

btw, to acommodate being able to sort a query by one of the pseudo-colums 'user_count' or 'userclass_value', i changed the order_by function to

Code:
function order_by($orderby, $direction = '')
{
    // Check if this is a related object
    if ( ! empty($this->related))
    {
        $this->db->order_by($this->table . '.' . $orderby, $direction);
    }
    else if( count($this->has_many) > 0 || count($this->has_one) > 0 )
    {
        $relations = array_merge($this->has_many, $this->has_one);
        
        $model = substr($orderby, 0, (strlen($orderby)-6));
        
        if( ! in_array($model, $relations))
        {
            $this->db->order_by($this->table . '.' . $orderby, $direction);
        }
        else
        {
            $this->db->order_by($orderby, $direction);
        }
    }
    else            
    {
        $this->db->order_by($orderby, $direction);
    }

    // For method chaining
    return $this;
}

which will trigger to order by 'userclass_value' or 'user_count' if 'userclass' and 'user were in fact models in has_one or has_many (conveniently '_value' and '_count' are 6 characters each), otherwise it will assume you are looking for a true column in the parent table and prepend the table name to prevent ambiguity. These are the only two kinds of pseudo columns you can order by so far, but again i'm working to develop this futher and would eventually like to be able to specify an array for display_value

also, you may be wondering how to search the value of a related item:

Code:
function where_related($model, $key, $value = NULL, $type = 'AND ', $escape = TRUE)
{
    $relations = array_merge($this->has_one, $this->has_many);
    
    if(in_array($model, $relations))
    {
        $where_table = $this->{$model}->table;
        
        //$where_value = $key == 'value' ? ( ! empty($this->{$model}->display_value) ? $this->{$model}->display_value : 'id') : 'id';
        
        $this->db->_where($where_table.'.'.$key, $value, $type, $escape);
    }
}

now, i can search through ANY of the userclass tables' columns

Code:
$user = new User();
$user->join_related();
$user->where_related('userclass', 'access >','2');
$user->order_by('userclass_name','asc');
$user->get();

vóila!.. well, it works now for searching thru has_one relations, and i'll be adding the ability to search has_many soon, too. So you could write

Code:
$userclass = new Userclass();
$userclass->join_related();
$userclass->where_count('user >','0');
$userclass->get();

// or even

$userclass->where('name','admin')->where_count('user >','0')->get();

i'm sure i'll be making more modifications on DataMapper to allow for more intricate searches through related items. I was a bit hesitant at first to try subselects, but now it seems its preferable to have one query with a subselect or two, versus up to several hundred separate queries, each of which task the DB and also require loading a new Datamap with its resources, etc..

I think i'm heading into the direction of changing the has_one and has_many arrays and their contents to allow for more dynamic relationship manipulation and explicit configuration, such as changing them to four arrays, each describing the object's relationship to the others:

Code:
$one_one    = array(); // Has and belongs to one object of the specified model
$one_many   = array(); // Belongs to many objects of the specified model
$many_one   = array(); // Many of these belong to one object
$many_many  = array(); // Many of these belong to many other objects

that would prove useful in deletes, updates & saves and such as well, but still have yet to get there.

anyway.. more later. any comments or ideas or criticism is greatly appreciated.

CC


DataMapper 1.6.0 - El Forum - 10-20-2008

[eluser]Philipp Gérard[/eluser]
Thank you very much stensi for this implementation of ActiveRecord, one less reason to learn Ruby ;-)

However, I am curious how using DatMapper instead of the built in ActiveRecord implementation in CI affects the overall speed of CI in production enviroments. Do you have any benchmarks on that?

Thanks in advance,
Philipp