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 - 11-30-2008

[eluser]stensi[/eluser]
Ah, brilliant. The copy of the corresponding field for the 'matches' rules is something I'd completely forgot about doing but now that you've reminded me, I've included a copy in both the get() and _related() methods. I've simplified it into a few lines and am making it always do a copy since that would be the correct behaviour. I can't think of a situation where you wouldn't want it defaulted to the matched value when loading an existing record.

Making the data easier to format when displaying in your HTML is something I've been thinking about since I read OverZealous's comments on how he was doing it.

One of the neat new methods I've added is a magic get_by_{field} method. So for example, you can do:

Code:
$u = new User();
$u->get_by_username($username);

echo $u->username;

Which is the same as:

Code:
$u = new User();
$u->where('username', $username)->get();

echo $u->username;

Obviously, you can use the get_by_{field} method for any field the object has, including get_by_id($id). In the same ease of use, I'm still figuring out the best way for an HTML display method. I was thinking something like this:

$object->display_{property}_as_{format}()

For example:

Code:
$p = new Product();
$p->get_by_id($id);

echo $p->name;
echo $p->display_cost_as_currency();
echo $p->display_description_as_html();

How's that sound? If you can think of a better way for displaying the property values, let me know!

Alternatively, but a lot more work, would be to create another class, say "Property", and have each field value stored as a Property object. When doing:

Code:
echo $p->cost;
echo $p->description;

It would simply echo the value as normal. But I'd add extra functionality in the Property class to allow:

Code:
echo $p->cost->currency();
echo $p->description->html();

Basically, each property would have several static display functions. Again, this would be a lot more work but it might make things more flexible in future. One downside is there'll be a slight increase in memory usage due to the additional Property objects.

Thoughts? Which way would you prefer? Also, can you see any other benefits or uses from having a Property class?

$object->display_{property}_as_{format}();
or
$object->{property}->{format}();

UPDATE: Just to clarify, I don't mind if the preferred choice is the one that requires more work. When adding new functionality, my main considerations are ease of use and extensibility. It doesn't matter to me how much work is involved, although it will mean you might have to wait longer Wink


DataMapper 1.6.0 - El Forum - 11-30-2008

[eluser]ntheorist[/eluser]
those two solutions sound great. I'm guessing you're talking about using the magic __call() method, no? Some people think its slow, but i think the ease of use for programming could be worth a small performance hit, which turns out isn't super bad (Here's some results on it)

i started creating a DataMapperField class, which would act as a factory to create objects based on field - type - value arguments. Each object type would extend an interface or abstract class with similar static methods

$object->set_value()
$object->get_value()
$object->display_value()
$object->form_element()
etc..

Then at some point you could turn each field into an object of the same name, and do exactly what you have in your second example. But yeah it is a lot of work! I haven't even gotten deep into writing it and i really like the ease of the first example.

so if i were forced to personally select an approach i'd give the magic method a first shot. Anyway, it's easier to write i'm sure and nothing would stop us from trying out the object creation method at a later date and benchmarking them side by side.

it would be great to have these magic methods

get_by_{$field}
get_{$field}_as_{$format}
get_{$field}_input (form element)

the one thing that comes up though, is how to handle related objects which are accessed just like fields, $object->{$value}. So if you tried, say get_{$related}_as_{$format}, how would that work?..

I've added a $primary_value varible to DM, defaulted to id and specified in a model/extend (picked this up from the kohana orm class, thx neovive!), and also added this tiny function
Code:
function get_value()
{
    $value = $this->primary_value;    
    return empty($this->{$value}) ? FALSE : $this->{$value};
}
so that helps a bit with dynamic html generation, which should handle relations like fields sometimes. Also, you could have a dynamic form dropdown function with (i'm writing this ad-hoc)
Code:
function get_dropdown( $limit, $offset, $where = NULL )
{
     $this->load->helper('form');

     $this->db->select('id, '.$this->primary_value);

     if( ! empty($where) )
     {
          $this->where($where);
     }
    
     $id = $this->id;

     $query = $this->db->get($this->table, $limit, $offset);

     return form_dropdown($this->model, $query->result_array(), $id);
}

that provides a base for extending models, which could always have the option of overloading the functions.

As far as more related model functionality goes, i'm working on an overall table-alias/field-alias scheme to allow for efficient SQL using multiple joins without risking table or field ambiguity. Also as i'm working with a join_key function, it can turn foreign key fields into referenceable data ie. SELECT user.id, user.friendID, friend.username as friend_username FROM '{user_table_name}' as user, left join (user_table_name) as friend on friend.id = user.friendID

anyway the relation issue is another topic.. would love to chat with you more about it too.

cheers,

CC


DataMapper 1.6.0 - El Forum - 11-30-2008

[eluser]stensi[/eluser]
On the "$object->get_{$related}_as_{$format}()" thing, that shouldn't be a problem since if you're displaying related data you shouldn't do it that way, but rather as "$object->related_object->get_{$field}_as_{$format}();". For example:

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

// Display username as HTML
$u->display_username_as_html();

// Display related group name as html
$u->group->display_name_as_html();

For now, I'm still undecided. I think in the end for me, it will depend on how much functional benefit there would be in turning properties into custom property objects. Right now I'm brainstorming what possible methods I would use on them, besides display methods, but without going overboard.

The primary value thing would be handy for general HTML type of things, such as with your drop-down method.

The related join stuff is something I've set aside to look at after I've completed all other tasks, since it usually ends up being a bit of a headache figuring out, lol. I'll definitely be picking your brain about it when I get up to that point! Smile


DataMapper 1.6.0 - El Forum - 11-30-2008

[eluser]OverZealous[/eluser]
I have one big concern about integrating these functions directly into DM - unnecessarily loading a lot of class data if it isn't needed. If I'm just looking up some value - and not displaying it - I don't want to load in the display or editing classes.

In my case, I'm mostly sending my data over JSON-formatted data, so there actually is no display or formatting code being used!

The reason I chose my method - using the "->v->${field}" or "->e-${field}" methods is that it allowed me to load up the class when it was needed, but completely silently. Also, this class only needed to be instantiated once each per object. not perfect, but not too bad. I might actually try to move more of my code into shared objects, eventually.

Of course, you could make this work just fine with stensi's method - using magic get_${field}_as_${type} - you would just have to load a rendering or editing class in the background. I think I have a way of combining the methodologies.

First, I would offer the magic fields display_${field}_as_${type} and edit_${field}_as_${type}, as stensi mentioned

Second, allow the special forms view_${field} and view_${field}_as_default (same for edit_) to exist, and to choose the type based on the (optional) $type parameter of the validations field, or default to simply calling htmlspecialchars() on the field. For editing, it should simply place the htmlspecialchars()'ed in a plain <input type="text" />. Also, the virtual properties (not methods) $view_${field} and $edit_${field} should call view_${field}_as_default() behind the scenes. This is very clean to read and write:
Code:
// example formatted this way because the website won't properly render <?= ?> tags
echo $user->view_firstname;
echo ' ';
echo $user->view_lastname;

Third, load a single class in that can handle the rendering, but only upon first access. The best case would be to store this class as a static object within DataMapper, or within each class, to reduce overhead. The methods would, obviously, be named after the ${type}.

Fourth, this class needs to be able to be overridden. In fact, I think it would be best if the default class was extremely basic - maybe only offer the defaults listed above. The name of the class could be described the way updated and created fields are, within the DM config, or the way model or table are, within the object. You could label it $field_displayer and $field_editor. Then, when instantiated, you could do:
Code:
if( ! isset( ($model)::$_dm_field_displayer ) ) {
    if( isset($this->field_displayer) ) {
        $this->load->library(strtolower($this->field_displayer));
        ($model)::$_dm_field_displayer = new $this->field_displayer;
    } else {
        if( ! isset(DataMapper::$_dm_field_displayer) ) {
            $model = // get model from datamapper config
            $this->load->library(strtolower($model));
            DataMapper::$_dm_field_displayer = new $model
        }
        ($model)::$_dm_field_displayer = DataMapper:$:_dm_field_displayer;
    }
}
// now look up function in ($model)::$_dm_field_displayer
This also allows for methods that need or can take arguments, such as display_${field}_as_limitedlength($length, $use_ellipsis)

Finally, I agree here with Stensi that the related objects should not be embedded within DM. In the end, the question is, how much should you combine your View with your Model and Controller. Of course, you really should keep them separate. That's why I prefer, somewhat, my method of using a virtual property.

As for me, I probably won't use it at all unless it is completely overwriteable. All of my editors rely on the Dojo JavaScript toolkit, and both my viewers and editors depend on localization code and the structure of the DM object. For example, I print out the complete XHTML form row, including a properly linked label tag, error messages, etc, from a simple ->e->name. It helps to keep my code as light as possible, and keeps 90% of my formatting in one spot.

Hopefully that is some help.

PS: I actually prefer the term "viewer" instead of "displayer", and therefore the function name view_${field}_as_${type}... But that's not terribly important.


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]stensi[/eluser]
Now that I've seen it, I would prefer the naming of view_... over display_... since it fits in better with the names already used by CodeIgniter.

It would be easy to provide "edit" versions using the form helper to spit it out as a form input. I'm assuming what you mean is that the view_... would be different formatting types (currency, phone_number, html) and edit_... would be different form input types (checkbox, text, dropdown)? Is that what you mean?

So, for example:

View:
Code:
// $object->view_{field}_as_{format}();

$u->mobile = 1234567890;
$u->active = TRUE;

echo $u->view_mobile_as_phone_number();
echo $u->view_active_as_image('gif');

// This might spit out:
1234 567 890
<img src="active_true.gif" />

Edit:
Code:
// $object->edit_{field}_as_{type}();
echo $u->edit_phone_number_as_text();
echo $u->edit_active_as_checkbox();

// Phone would have a text input
// Active would have a ticked checkbox since active is TRUE

The above is theoretical. Just trying to clarify preference.

If I was to go ahead with this stuff I would make a method for each type of "format" and "type" option, so they can be overridden. As all formats and types would be standard, it would be stored as a single static variable for all DataMapper objects.


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]OverZealous[/eluser]
I actually prefer the same "types" to be used in editing and viewing.

For example, if I am working with a date field:
Code:
// in the Post class
$validation = array(
    'editdate' => array(
        'label' => 'Edit Date',
        'rules' => array(...),
        'type' => 'date'
    ),
    // ...
);

// ------------------

// in a view
$post->view_editdate(); // outputs the field as a formatted date
//or
$post->edit_editdate(); // outputs the field in a textbox, formatted for editing, maybe including a popup calendar for selecting dates.

For many formatting types, the editor will just defer to the default single line editor.

For checkboxes, I actually used the phrase "checkbox" both times, since I wanted the field to be rendered as a graphic of a checkbox selected or not:
Code:
// in viewer
// renders a field as a checkbox
function checkbox($object, $field) {
    $value = $object->${field};
    if($value) {
        echo '<img src="images/checkbox_checked.png" alt="Yes"/>';
    } else {
        echo '<img src="images/checkbox_unchecked.png" alt="No"/>';
    }
}

For selection lists, I either passed in a list of values or I looked up a standard array within the object
Code:
// in viewer
function selection($object, $field, $values = NULL) {
    $value = $object;
    if(is_null($values)) {
        // look for values array in both object and class
        if(isset($object->{$field.'_values'})) {
            $values = $object->{$field.'_values'};
        } else if(isset($object::{$field.'_values'})) {
            $values = $object::{$field.'_values'};
        } else {
            show_error("No values defined");
        }
    }
    return htmlspecialchars($values[$value]);
}
// edit method is similar, or shares code with a common function to get the array

This allowed me to look up how to render the field for editing or viewing using $this->validation[$field]['type'].

I also often like to have several options. For example, the select list above could be reformatted as a list of radio buttons, if it made sense. My code has a function called list() that automatically switches to radios for less than 4 items.

Just some ideas, referencing some of the problems I have had. I've actually spent a ton of time refining my Html_viewer and Html_editor classes (that I believe you have a copy of, stensi). Even though they are formatted for Dojo, they offer a lot of code for formatting a variety of fields. For example, Dates, Times, and Timestamps can be formatted as 'SHORT', 'MEDIUM', or 'LONG', or custom formatted using the strftime() function.

Probably the biggest concern here is that every user of the system is going to want different formatting for their output, both on view and edit. This is why I am not sure how much you want to include. I think that providing a skeleton for the viewer and editor would be more useful, as it would be a guide for new users on how to add functionality. You can see just how different a simple thing such as a boolean/checkbox field could be rendered (various images, true/false, yes/no, on/off, or even more specific, like lost/found)!


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]stensi[/eluser]
Adding the type setting in the validation array is something I'd probably do. I'm using the field_data() method to get the fields of a table now, instead of list_fields(), and that comes back with additional meta data, such as the type of each field. So, if a type does not exist in the validation array, I can fall back on the type listed in the meta data.

I'd try not to go as far as giving set options on how things get formatted, such as SHORT/MEDIUM/LONG for dates. The developer would need to supply the formatting information but I would at least have a default, and probably add an additional setting in the validation array for a developer specified default, such as format. For example, formatting by date, the developer would specify the format in the same way you do with PHP's date function:

Code:
// If we had a "type" set in the validation array for the "created" field
// and it was of type "date", this would output the default format for the date
// and if we had a "format" set as well, it would use that specified format
echo $u->view_created();

// Defining "type" and "format" in the method call
// this would outputs "created" as a date in the specified format
echo $u->view_created_as_date('F j, Y, g:i a');

This is all something I've only started looking at today so I'm not sure if I'll be doing anything on it in the coming version. I'll mess around with it a bit tomorrow and see how far I get.


UPDATE:

Just looking at the other idea, of having each property turned into an object via a very small property class, I was thinking of just doing something simple like making it use any loaded helper or PHP function that accepts one or more parameters and apply the result to itself. For example:

Code:
$u = new User();
echo $u->username->htmlentities();

// Same as doing:
echo $u->username = htmlentities($u->username);

You could also chain it:

Code:
echo $u->username->trim()->htmlentities();

// Same as doing:
echo $u->username = htmlentities(trim($u->username));

You'd only really want to use prepping type functions.

For those that accept more than one parameter, it would only work correctly "out of the box" if the first parameter is the value being used (ie, the property value). So unfortunately, since the date function has the first parameter as the format and the second as the value being used, it can't get used as is. I'd need to make a mini-override that passes through the parameters swapped around, so I could still make it work. An example of one that would work "out of the box" with multiple parameters is str_pad:

Code:
echo $u->username->str_pad(10, "_", STR_PAD_BOTH);

// Same as doing:
echo $u->username = str_pad($u->username, 10, "_", STR_PAD_BOTH);

Again, these are just ideas I'm throwing around for discussion. I've made no decision yet on what I'll be going ahead with as far as all this "formatting values" goes.


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]ntheorist[/eluser]
I agree wholeheartedly that any additional classes should be loaded only as needed. The great thing about using the magic methods though is that this can be achieved regardless of how a viewing or editing class is implemented. Whatever can be done to reduce the size of the classes, yet keep with good clean code is the way to go. It seems like we are all taking slightly different approaches but trying to achieve the same thing.

right now i'm actually trying to strip datamapper of everything besides database functionality (yes, including the validation), and then the other classes i'm creating are loaded explicitly in the controller, and act as 'clothes' for datamapper.. so when you call up the listing class and load the datamap into it, $datamap->{value} becomes $list->{value-formatted}, or $form->{value-input}, but again it's only loaded when called. That's similar to the $model->v->{$value} implementation you have, just not using the __get() magic method. I know that having the validation within DM is great, i just think that in most cases the only time you really NEED to validate is when dealing with user input, ie forms, and so it's handled with the form class for DM, otherwise many times the values are set by code and don't need to be validated to be saved (making for faster processing).

Also, i'm still working out the form generation for a datamap, and in some instances i'm adding another element to the validation array, 'conf', which holds whatever necessary config data i need for processing. This is where i keep the lists of say a select field
Code:
'status' => array(
    'field' => 'status',
    'type'    => 'list',
    'label' => 'Status',
    'conf' => array(
        'list_values' => array('1' => 'Active','2' => 'Inactive','3' => 'Suspended','4'=>'Pending'),
        'list_default' => '1')
    'rules' => array()
),
So when i do create the form element for that field, it knows to use a dropdown by its type (list) and then just grabs the list from 'conf' and inserts it directly into a form_dropdown(), also you can imagine that you could add css classes to each value in conf. That's all handled by the form class.

As far as embedding relations in DM, if we do plan on implementing effecient sql using joins then DM should at least be able to do that and set them up. I actually overwrote the get and _to_object methods to try to clean up my own code. And in the form class i'm using, relations are validated as well, and also some have their own config (how else would you specify that a certain model MUST have one kind of relation?)
Code:
var $has_one = array(
    'userclass' => array(
        'label' => 'User Class',
        'rules' => array('required')
    )
);
Also, i've built two special models, 'document' and 'photo', which can't be validated within the post array and need special handling/configuration. So in a model that relates to a pdf:
Code:
var $has_one = array(
    'document' => array(
        'label'     => 'Manual PDF',
        'conf' => array(
            'upload_path' => '../content/manuals/',
            'allowed_types' => 'pdf',
            'max_size' => 8000,
            'encrypt_name' => FALSE,
            'remove_spaces' => TRUE
        ),
        'rules' => array('required')
    )
);
Then the config is passed to the document model, and $document->upload() is called and either returns TRUE or passes any errors back to the form class for display. Having the photo model is turning out handy indeed as i can specify thumbnail size, specific proportions and size per each model that has one, plus i am working on functions for editing photos, like cropping, resizing or filtering. So while datamapper shouldn't necessarily be aware of or handle its relations on it's own, i think they can benefit a lot from more additions in the models themselves.

anyway, i'm glad we're all collaborating with ideas for DM. I am myself by no means an expert on any of this stuff.. I can tell you i've been working hard with it for the last 2 months or so and it can be very frustrating! But i am excited to see what stensi comes up with for it, and I hope all our ideas are helping him.

thx,

CC


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]OverZealous[/eluser]
I like the idea of storing the array of values within the validation array - but it doesn't allow for statically stored values (except, that's being fixed in the next generation of DataMapper). If the values are not stored statically, every instance of the class will have a copy, leading to unnecessary RAM usage on large data sets.

I also like the idea of adding rules to the $has_one and $has_many arrays. In fact, I'm wondering if the validation array should just be combined into the $fields array, then all three would be more consistent - all would be associative arrays combining the name of the field with field properties. That's a pretty big change, I realize!
Code:
class User extends DataMapper {
    $has_one = array(
        'address' => array(
            'label' => '@user_address_label', // possible method of looking up labels using localization
            'rules' => array('required')
        )
    );
    $has_many = array(
        'phone' => array(
            'label' => '@user_phone_label',
            'rules' => array('required')
        )
    );
    $fields = array(
        'firstname' => array(
            'label' => '@user_firstname_label',
            'rules' => array('required')
        )
    );
}

(Don't forget that you can easily check keys using array_key_exists() - it's basically the same as using in_array(), and just as easy to read.)

I'm not sure there is a good reason to push the values array (and other config items) into yet another array ('conf'). Instead, I would just place configuration details right within the main $fields / $validation array. It's one less layer of abstraction, and one less hash lookup. That feels like a personal preference, though. I can see how it is easier to read with the conf array.

The way we're going, a DM class will be completely declarative! You can define most fields in the arrays at the top, with no methods or class fields needed.

I still like the validation stuff, but that's because my application has 95% user-input, so it wouldn't provide much benefit for me. I wonder if, instead, it would make sense to have a function that saves the data without validating. Same basic benefit, but without the overhead and complexity of separating validation from DM. Maybe just add a parameter:
Code:
save($object = NULL, $validate = TRUE) {
    // ...
    if( ! $validate || $this->validate() ) {
        // ...
    }
}
// ...
$obj->save(NULL, TRUE);

All good stuff! Poor stensi ;-)


DataMapper 1.6.0 - El Forum - 12-01-2008

[eluser]Muser[/eluser]
Hi all,

I've started a new project, and I am using Datamapper with HMVC together. It's compatible?
I have an strange issue with DM 1.4.5...

User model
------------------------
Code:
class User extends Datamapper
{
    var $has_one = array('province');
...
}


Province model
------------------------
Code:
class Province extends Datamapper
{
...
}

Inside a controller I can get user<->province relation with no problem:

Code:
$user = new User();
$user->where('id',5)->get();
echo $user->name; //OK
echo $user->province->get()->name; //OK!

But, inside a module controller (module dashboard, controller profile) I've been trying the same, and echoes NULL:

Code:
$user = new User();
$user->where('id',5)->get();
echo $user->name; //OK
echo $user->province->get()->name; // NULL ?
Is anything related about autoloading (function autoload inside Datamapper class), because of only is searching inside APPPATH.'/models' ?

Thank you for this great library!!