Coder Social home page Coder Social logo

codeigniter-base-model's Introduction

DEPRECATED: codeigniter-base-model

Deprecated since I no longer use CodeIgniter. If anybody would like to take over maintainence of this repo, please get in touch.

No Maintenance Intended Build Status

My CodeIgniter Base Model is an extended CI_Model class to use in your CodeIgniter applications. It provides a full CRUD base to make developing database interactions easier and quicker, as well as an event-based observer system, in-model data validation, intelligent table name guessing and soft delete.

Synopsis

class Post_model extends MY_Model { }

$this->load->model('post_model', 'post');

$this->post->get_all();

$this->post->get(1);
$this->post->get_by('title', 'Pigs CAN Fly!');
$this->post->get_many_by('status', 'open');

$this->post->insert(array(
    'status' => 'open',
    'title' => "I'm too sexy for my shirt"
));

$this->post->update(1, array( 'status' => 'closed' ));

$this->post->delete(1);

Installation/Usage

Download and drag the MY_Model.php file into your application/core folder. CodeIgniter will load and initialise this class automatically for you.

Extend your model classes from MY_Model and all the functionality will be baked in automatically.

Naming Conventions

This class will try to guess the name of the table to use, by finding the plural of the class name.

For instance:

class Post_model extends MY_Model { }

...will guess a table name of posts. It also works with _m:

class Book_m extends MY_Model { }

...will guess books.

If you need to set it to something else, you can declare the $_table instance variable and set it to the table name:

class Post_model extends MY_Model
{
    public $_table = 'blogposts';
}

Some of the CRUD functions also assume that your primary key ID column is called 'id'. You can overwrite this functionality by setting the $primary_key instance variable:

class Post_model extends MY_Model
{
    public $primary_key = 'post_id';
}

Callbacks/Observers

There are many times when you'll need to alter your model data before it's inserted or returned. This could be adding timestamps, pulling in relationships or deleting dependent rows. The MVC pattern states that these sorts of operations need to go in the model. In order to facilitate this, MY_Model contains a series of callbacks/observers -- methods that will be called at certain points.

The full list of observers are as follows:

  • $before_create
  • $after_create
  • $before_update
  • $after_update
  • $before_get
  • $after_get
  • $before_delete
  • $after_delete

These are instance variables usually defined at the class level. They are arrays of methods on this class to be called at certain points. An example:

class Book_model extends MY_Model
{
    public $before_create = array( 'timestamps' );
    
    protected function timestamps($book)
    {
        $book['created_at'] = $book['updated_at'] = date('Y-m-d H:i:s');
        return $book;
    }
}

Remember to always always always return the $row object you're passed. Each observer overwrites its predecessor's data, sequentially, in the order the observers are defined.

Observers can also take parameters in their name, much like CodeIgniter's Form Validation library. Parameters are then accessed in $this->callback_parameters:

public $before_create = array( 'data_process(name)' );
public $before_update = array( 'data_process(date)' );

protected function data_process($row)
{
    $row[$this->callback_parameters[0]] = $this->_process($row[$this->callback_parameters[0]]);

    return $row;
}

Validation

MY_Model uses CodeIgniter's built in form validation to validate data on insert.

You can enable validation by setting the $validate instance to the usual form validation library rules array:

class User_model extends MY_Model
{
    public $validate = array(
        array( 'field' => 'email', 
               'label' => 'email',
               'rules' => 'required|valid_email|is_unique[users.email]' ),
        array( 'field' => 'password',
               'label' => 'password',
               'rules' => 'required' ),
        array( 'field' => 'password_confirmation',
               'label' => 'confirm password',
               'rules' => 'required|matches[password]' ),
    );
}

Anything valid in the form validation library can be used here. To find out more about the rules array, please view the library's documentation.

With this array set, each call to insert() or update() will validate the data before allowing the query to be run. Unlike the CodeIgniter validation library, this won't validate the POST data, rather, it validates the data passed directly through.

You can skip the validation with skip_validation():

$this->user_model->skip_validation();
$this->user_model->insert(array( 'email' => 'blah' ));

Alternatively, pass through a TRUE to insert():

$this->user_model->insert(array( 'email' => 'blah' ), TRUE);

Under the hood, this calls validate().

Protected Attributes

If you're lazy like me, you'll be grabbing the data from the form and throwing it straight into the model. While some of the pitfalls of this can be avoided with validation, it's a very dangerous way of entering data; any attribute on the model (any column in the table) could be modified, including the ID.

To prevent this from happening, MY_Model supports protected attributes. These are columns of data that cannot be modified.

You can set protected attributes with the $protected_attributes array:

class Post_model extends MY_Model
{
    public $protected_attributes = array( 'id', 'hash' );
}

Now, when insert or update is called, the attributes will automatically be removed from the array, and, thus, protected:

$this->post_model->insert(array(
    'id' => 2,
    'hash' => 'aqe3fwrga23fw243fWE',
    'title' => 'A new post'
));

// SQL: INSERT INTO posts (title) VALUES ('A new post')

Relationships

MY_Model now has support for basic belongs_to and has_many relationships. These relationships are easy to define:

class Post_model extends MY_Model
{
    public $belongs_to = array( 'author' );
    public $has_many = array( 'comments' );
}

It will assume that a MY_Model API-compatible model with the singular relationship's name has been defined. By default, this will be relationship_model. The above example, for instance, would require two other models:

class Author_model extends MY_Model { }
class Comment_model extends MY_Model { }

If you'd like to customise this, you can pass through the model name as a parameter:

class Post_model extends MY_Model
{
    public $belongs_to = array( 'author' => array( 'model' => 'author_m' ) );
    public $has_many = array( 'comments' => array( 'model' => 'model_comments' ) );
}

You can then access your related data using the with() method:

$post = $this->post_model->with('author')
                         ->with('comments')
                         ->get(1);

The related data will be embedded in the returned value from get:

echo $post->author->name;

foreach ($post->comments as $comment)
{
    echo $message;
}

Separate queries will be run to select the data, so where performance is important, a separate JOIN and SELECT call is recommended.

The primary key can also be configured. For belongs_to calls, the related key is on the current object, not the foreign one. Pseudocode:

SELECT * FROM authors WHERE id = $post->author_id

...and for a has_many call:

SELECT * FROM comments WHERE post_id = $post->id

To change this, use the primary_key value when configuring:

class Post_model extends MY_Model
{
    public $belongs_to = array( 'author' => array( 'primary_key' => 'post_author_id' ) );
    public $has_many = array( 'comments' => array( 'primary_key' => 'parent_post_id' ) );
}

Arrays vs Objects

By default, MY_Model is setup to return objects using CodeIgniter's QB's row() and result() methods. If you'd like to use their array counterparts, there are a couple of ways of customising the model.

If you'd like all your calls to use the array methods, you can set the $return_type variable to array.

class Book_model extends MY_Model
{
    protected $return_type = 'array';
}

If you'd like just your next call to return a specific type, there are two scoping methods you can use:

$this->book_model->as_array()
                 ->get(1);
$this->book_model->as_object()
                 ->get_by('column', 'value');

Soft Delete

By default, the delete mechanism works with an SQL DELETE statement. However, you might not want to destroy the data, you might instead want to perform a 'soft delete'.

If you enable soft deleting, the deleted row will be marked as deleted rather than actually being removed from the database.

Take, for example, a Book_model:

class Book_model extends MY_Model { }

We can enable soft delete by setting the $this->soft_delete key:

class Book_model extends MY_Model
{ 
    protected $soft_delete = TRUE;
}

By default, MY_Model expects a TINYINT or INT column named deleted. If you'd like to customise this, you can set $soft_delete_key:

class Book_model extends MY_Model
{ 
    protected $soft_delete = TRUE;
    protected $soft_delete_key = 'book_deleted_status';
}

Now, when you make a call to any of the get_ methods, a constraint will be added to not withdraw deleted columns:

=> $this->book_model->get_by('user_id', 1);
-> SELECT * FROM books WHERE user_id = 1 AND deleted = 0

If you'd like to include deleted columns, you can use the with_deleted() scope:

=> $this->book_model->with_deleted()->get_by('user_id', 1);
-> SELECT * FROM books WHERE user_id = 1

If you'd like to include only the columns that have been deleted, you can use the only_deleted() scope:

=> $this->book_model->only_deleted()->get_by('user_id', 1);
-> SELECT * FROM books WHERE user_id = 1 AND deleted = 1

Built-in Observers

MY_Model contains a few built-in observers for things I've found I've added to most of my models.

The timestamps (MySQL compatible) created_at and updated_at are now available as built-in observers:

class Post_model extends MY_Model
{
    public $before_create = array( 'created_at', 'updated_at' );
    public $before_update = array( 'updated_at' );
}

MY_Model also contains serialisation observers for serialising and unserialising native PHP objects. This allows you to pass complex structures like arrays and objects into rows and have it be serialised automatically in the background. Call the serialize and unserialize observers with the column name(s) as a parameter:

class Event_model extends MY_Model
{
    public $before_create = array( 'serialize(seat_types)' );
    public $before_update = array( 'serialize(seat_types)' );
    public $after_get = array( 'unserialize(seat_types)' );
}

Database Connection

The class will automatically use the default database connection, and even load it for you if you haven't yet.

You can specify a database connection on a per-model basis by declaring the $_db_group instance variable. This is equivalent to calling $this->db->database($this->_db_group, TRUE).

See "Connecting to your Database" for more information.

class Post_model extends MY_Model
{
    public $_db_group = 'group_name';
}

Unit Tests

MY_Model contains a robust set of unit tests to ensure that the system works as planned.

Install the testing framework (PHPUnit) with Composer:

$ curl -s https://getcomposer.org/installer | php
$ php composer.phar install

You can then run the tests using the vendor/bin/phpunit binary and specify the tests file:

$ vendor/bin/phpunit

Contributing to MY_Model

If you find a bug or want to add a feature to MY_Model, great! In order to make it easier and quicker for me to verify and merge changes in, it would be amazing if you could follow these few basic steps:

  1. Fork the project.
  2. Branch out into a new branch. git checkout -b name_of_new_feature_or_bug
  3. Make your feature addition or bug fix.
  4. Add tests for it. This is important so I don’t break it in a future version unintentionally.
  5. Commit.
  6. Send me a pull request!

Other Documentation

Changelog

Version 2.0.0

  • Added support for soft deletes
  • Removed Composer support. Great system, CI makes it difficult to use for MY_ classes
  • Fixed up all problems with callbacks and consolidated into single trigger method
  • Added support for relationships
  • Added built-in timestamp observers
  • The DB connection can now be manually set with $this->_db, rather than relying on the $active_group
  • Callbacks can also now take parameters when setting in callback array
  • Added support for column serialisation
  • Added support for protected attributes
  • Added a truncate() method

Version 1.3.0

  • Added support for array return types using $return_type variable and as_array() and as_object() methods
  • Added PHP5.3 support for the test suite
  • Removed the deprecated MY_Model() constructor
  • Fixed an issue with after_create callbacks (thanks zbrox!)
  • Composer package will now autoload the file
  • Fixed the callback example by returning the given/modified data (thanks druu!)
  • Change order of operations in _fetch_table() (thanks JustinBusschau!)

Version 1.2.0

  • Bugfix to update_many()
  • Added getters for table name and skip validation
  • Fix to callback functionality (thanks titosemi!)
  • Vastly improved documentation
  • Added a get_next_id() method (thanks gbaldera!)
  • Added a set of unit tests
  • Added support for Composer

Version 1.0.0 - 1.1.0

  • Initial Releases

codeigniter-base-model's People

Contributors

akenroberts avatar akpwebdesign avatar andytime avatar benedmunds avatar ds0nt avatar eweap avatar gbaldera avatar inda5th avatar itguy614 avatar jamierumbelow avatar jrmadsen67 avatar kamote avatar mayurvirkar avatar mogetutu avatar mpmont avatar mrlnx avatar ronisaha avatar thefuzzy0ne avatar titosemi avatar tonydew avatar yavork avatar zbrox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

codeigniter-base-model's Issues

add like sql.

public function like($campo, $dato, $fitro = "both") {
    $this->db->like($campo, $dato, $filtro);
    return $this;
}

Specifying different primary key not working

Line 498:

$row->{$relationship} = $this->{$options['model']}->get_many_by($options['primary_key'], $row->{$this->primary_key});

This fixes it:
$row->{$relationship} = $this->{$options['model']}->get_many_by($options['primary_key'], $row->{$options['primary_key']});

Changed $this->primary_key to $options['primary_key']

insert() tries to insert $_POST data when using validation

insert() calls _run_validation() which uses and returns $_POST. If it validates, it tries to insert $_POST as a new record. Everything is fine when your form inputs are in the database table, but when they're not, the insert fails. This can be circumvented be clearing $_POST before calling insert() or by using $protected_attributes (which would be fine if you always knew what params are in $_POST, which I don't in this case because the forms are dynamic). IMHO, $_POST should be saved and restored before returning from _run_validation() and the validated $data should be returned.

Cannot retrieve protected property values from outside classes

Properties of MY_Model are currently either private or protected. There is currently no way to retrieve class properties of MY_Model from outside classes (i.e: a CI_Controller class). I noticed this when attempting to access MY_Model::$soft_delete_key from a controller.

The solutions I have thought of would be:

  1. Overload the properties using __get()
  2. Implement a get method for each property individually (i.e: MY_Controller::get_soft_delete_key()

More support for joins, maybe base it on a naming convention?

Hello again,

I really like the work you're doing, but so far I don't see the model having too much automatic join support. This could be interesting to implement (although one might argue that we'd just be reinventing the active record pattern which isn't actually present in CI).

Each model could have a list of tables which whose keys are contained in the current table as foreign keys. By using a convention such as "id" . $otherTableName for the foreign keys, whenever someone would ask the model to retrieve some data, it would simply automatically build the joins dynamically.

The same could be done for inserting updating data. Validation could prove to be an issue, but it could be solved by loading the appropriate validation rules from a config. (we'd just have to politely ask whoever is using this base model to store his rules in a config; it's better anyway).

Some sort of constraints could be added when deleting data as well. Like :dependent => :destroy works in Rails.

What do you think?
Andrei Bârsan

After_delete callbacks

Hi,

I wanted to change the native "delete" returns, so that they return ($this->db->affected_rows() > 0).

This way, they return TRUE if anything was deleted, and FALSE if nothing was deleted. I think this is better than the default CI + mySQL methods (which is return TRUE if the "operation" was successful, even if nothing was deleted).

So I tried to do a $after_delete callback. However I've discovered that you cant alter the $result from a delete using callbacks.

So I changed lines 311, 326 and 339 from:

$this->_run_after_callbacks('delete', array( $id, $result ));

to

$result = $this->_run_after_callbacks('delete', array( $id, $result ));

Because $result is passed to the callback, you can just return the default $result, and keep your logic the same. Or you can change the $result in your callback, and alter the overall outcome.

Using with() across a chain of relationships

I was looking at doing something like this:

  • town
  • county
  • country

town belongs to county, which belongs to country

doing
town_model->with('county')->with('country)->get(xx)

it only gives me the direct relationship, i.e. county -- no nested relates callback happens for the county...
am I missing something or is this a new feature request ?

MANY THANKS FOR EXCELLENT WORK!
Find it much easier to deal with this than the bloat ware D2...

Before create callback not working

FYI - in the current version of MY_Model, the before_callback is NOT working. What is happening is the callback runs, but any returned new data is NOT inserted, it still inserts the OLD data (from before the callback).

I fixed it by simply using the _run_before_callback code from the last version of MY_Model and pasting it into the new version - it now works as expected.

Hi,The soft delete is great! I have some advice

line 395 of the latest core code..
$where = $this->trigger('before_delete', $where);
after this code,you use $this->db->..... so ,the result of latest $where nerver worked.
I don't know wheather I am right. if I was wrong,I am sorry.

If I want to add the soft delete time before do the actual update operation,It 's a problem.
My way is
line :399
$result = $this->update_all(array( $this->soft_delete_key => TRUE ));
use your own function to do that ,if I want to add some value before soft delete,
I can use the trigger of before_update and judge wheather the deleted is true

May be it is so bad.... what is your way?

A question with Callbacks/Observers

after created a feedback, Send a message, But that does not work, I write code like this

class Feedback_model extends MY_Model
{
public $after_create = array('add_message');

protected function add_message($data)
    {       
    $msg = array('subject' => 'You have message from '.$data['name']);

    $this->db->insert('messages', $msg);        

            return $data;
     }

}

Cannot access empty property

I have taken a look at the base model but cannot understand why this doesn't work.

Line: 457: $row->{$relationship} = $this->{$segment[1]}->get($row->{$options['primary_key']});

It keeps crashing saying "Message: Undefined offset: 1".

Can anybody help me figure out what the problem is? I am using PHP 5.2 could this be the issue?

Edit:

Apparently $segment[1] doesn't exist. I had to change it to $segment[0].

Will this have any effect elsewhere?

delete_by() function doesn't have correct return, possibly others

The function delete_by() will always return 1, even if nothing was deleted. To fix this the function should return $this->db->affected_rows(); instead of $result

Detailed example below with added code for debugging:

  /**
     * Delete a row from the database table by an arbitrary WHERE clause
     */
    public function delete_by()
    {
        $where = func_get_args();
        $this->_set_where($where);

        // added comment
        log_message('error', '$where params are:' . print_r($where, true));

        $where = $this->trigger('before_delete', $where);

        if ($this->soft_delete)
        {
            $result = $this->db->update($this->_table, array( $this->soft_delete_key => TRUE ));
        }
        else
        {
            $result = $this->db->delete($this->_table);
        }

        $this->trigger('after_delete', $result);

        // added comment
        log_message('error', '$result is:' . print_r($result, true));
        log_message('error', 'affected->rows is:' . print_r($this->db->affected_rows(), true));

        return $result;

    }

When you run the above code you'll notice that $result is always 1, even when nothing was deleted or updated. In comparison $this->db->affected_rows is accurate, showing 0 if nothing was updated and the number of affected rows if something was updated or deleted.

This same return happens in a few other spots, namely the update and delete functions.

The only logic I see for possibly returning $return is if we wanted to return the ACTUAL affected rows in cases of update, sending them back to the function as an object or array. However in this case I think you need to do another get using the updated ids, not 100% sure though.

multiple callbacks data problem

just found if you call multiple callbacks each callback replace data and changes of previous callback i used update_before and insert_before callbacks

$this->update_before = array('callback1','callback2');

here current code of _run_before_callbacks

private function _run_before_callbacks($type, $params = array())
    {
        $name = 'before_' . $type;
        $data = (isset($params[0])) ? $params[0] : FALSE;

        if (!empty($this->$name))
        {
            foreach ($this->$name as $method)
            {
                $data = call_user_func_array(array($this, $method), $params);
            }
        }

        return $data;
    }

we will get data changes from callback2 not from callback1

here i have modified _run_before_callbacks

private function _run_before_callbacks($type, $params = array())
    {
        $name = 'before_' . $type;

        if (!empty($this->$name))
        {
            foreach ($this->$name as $method)
            {
                $params[0] = call_user_func_array(array($this, $method), $params);
            }
        }

        $data = (isset($params[0])) ? $params[0] : FALSE;

        return $data;
    }

same will go for _run_after_callbacks

Proposal: add get_first() method?

First of all: thanks for your awesome model!!

If I need a first record of an ordered set I use:

$this->$model->order_by($order_bys)
     ->$model->get_by(array());

Because get() expects an primary key, and get_by expects a where clause.

Should there be an easier way? Or am I overlooking something?

Thanks!

Allow third argument for dropdown() for a WHERE clause.

There are times where I want to use the dropdown() utility but also don't want certain elements showing up in the dropdown. Just need to change the if/else that counts $args and have a case where it's 3 and call _set_where() with the third argument.

Request: Catching errors

Many lines of code would be saved if we could catch simple errors with the base model and have an error message echoed and/or logged. You know all those infrequent errors that you want to check for but don't want to make a pretty user friendly interface for handling since it's broke and any how only the man in charge can fix it. Like this:

->on_error('Foobar missing.')
->get_all(...);

->on_error('Multiple Foobars matching criteria.')
->get_one(...);

Could even have a default error message so that just this would do the trick:

->on_error()
->get_all(...);

Brilliant or simply stupid?

$has_many and get_all() returns relationship for first value only.

In function relate(){} just before return $row; you clear the $this->_with variable by setting it back to an empty array. This causes the relationship to only fire on the first row returned, after which if(in_array($relationship, $this->_with){} will not fire. Removing $this->_with = array(); from the end of the function causes it to fire properly on a $has_many relationship.

My test case is:

class Image_model extends MY_Model(){

    public $has_many = array( "image_description" );

}

class Image_description_model extends MY_Model {

    public $belongs_to = array( "image" );

}

And inside the controller:

$data['images'] = $this->image_model->with('image_description')->limit( 50, 0 )->get_all();

MY_Model::get_all() returns soft deleted records

For consistency, shouldn't MY_Model::get_all() do the soft deleted checks that the other get methods do? It just seems a little inconsistent for it not to. Or perhaps another method should be added, such as MY_Model::get_all_not_deleted()?

If we use $this->model->with_deleted() to show soft deleted records for the other get methods, it just seems that get_all() should follow the same methodology.

Protected attribute bug

Hi,

I just started to use the library and noticed that the "protected_attributes" array can be set but it does not appear to be used. For example, I set:

$this->protected_attributes = array( $this->primary_key, 'event_start_date' );

When calling the insert function without validation I get an error:

Unknown column 'event_start_date' in 'field list'

I would expect that the protect_attributes function would be called automatically with and without validation - is that true?

Best.

Ernie

When using with() or belongs_to(), the query fails if the original row isn't found.

The after_get call to relate() is being called whether or not there were rows found for the request, however relate() assumes something was found, so it continues to find relationships even though it doesn't need to.

Use case: have products with multiple variants. On the storefront, grabbing a product with many variants. If the product doesn't exist, I get PHP errors because it's still trying to grab relationships.

$this->data['product'] = $this->product->with('variants')->get_by('slug', $slug);

I could grab the product first, and then grab the variants to get around this error, but it should be handled internally.

Solution would be to check for empty($row) first thing in relate() and if it's empty, just do a return. I'll try to patch it and write some tests, but can't promise anything on the tests front.

Performance of with() compared to join

Hey,

This isn't an issue, but a question. I didn't know where else to ask it though so I hope this place is ok.

What would be better performance-wise if you want to get say a 100 records with relationships like belongs_to or has_many? Since the with() implementation is per record, for each record there will be 1 aditional query to the DB to get the relationship data. So 100 more queries. Since it will be probably a fairly simple query, maybe it'll be as fast as the join? Does anyone has any data on that? Because I was thinking if maybe it can be optimized to have 1 query per model call and not per record. But I really am not sure if the extra complications are needed if performance doesn't suffer.

Thanks.

count_all

Is it a bug that count_all does not use the before_get and after_gel callbacks?

In my scenario I have the before_get callback used to always add "where user_id = current_user_id' - because users will only ever get their own records. So a "count_all" I was expecting to get the users count.

Add support for result sets as arrays

As far as I can see, the MY_Model core library is able to return row data as stdClass data type. I mean the get* methods. The library may be extended to support also array type row data in the same manner as CodeIgniter does it.

Additional methods may be added (this is easy) as follows:
public function get_array($primary_value) {...}
public function get_by_array() {...}
public function get_many_array($values) {...}
public function get_many_by_array() {...}
public function get_all_array() {...}

I am not quite sure, but maybe additional arrays for storing callbacks should be defined:
protected $before_get_array = array();
protected $after_get_array = array();

A proposal: exists() method

/**
 * Checks whether a single record based on the primary key exists.
 * @return boolean
 */
public function exists($primary_value)
{
    ...
}

The use case: When an id is received through $_GET parameter, URL segment, etc, it may be validated in a simple way, without getting the whole record.

I don't think that callback functions are to be triggered.

Different method of validation

The current method of validation overwrites the POST method -- isn't this a bit tricky? If the model encounters any problems and a form must be re displayed to the user, the values shown might differ from what was originally passed depending on your code. (for example, if you have a localized value and you convert it for use in the model, then it'd spit out the unlocalized value to the user)

How to work with many tables related to each other?

Hi,
I've just started using this base model and I'm refactoring a lot of my existing code.

I read the documentation and the articles suggested in the readme, but still I'm not sure how to proceed in a situation where I have a join table.

I mean, for example:
I have a Books table, an Authors table and an Authors_books table. In order to use MY_model, shall I create a model for each table, even for the Authors_books one?

Or, in a more complex scenario, I have to find the price of a product which is based on values retrieved from other three tables. What would be the best practice here?

Prepping validation functions ignored?

It appears that prepping functions in the model are not functioning in MY_Model. The 'Rule' (boolean) functions work perfectly, but the 'Prepping' (string manipulation, etc) functions seem to be completely disregarded.

Is this by design or a known thing?

I've setup a small example of the exact same validation array used in the standard CI fashion along side your MY_Model method if you'd like to take a look:

https://github.com/tonydew/my_model_validation_test

problem accessing two or more databases.

My problem is this.

My application has access to two databases.

In the second model that accesses the database I put $ _db = 'second'.

The problem occurs when I call two models in the controller. (one on one connection and the other on connection).

The model does not understand what database corresponds to it, because the builder my_model it defines which bank it connects. Happens to write one above the other.

dropdown() problem with _run_after_callbacks()

When calling the dropdown() function I get the following error:

A PHP Error was encountered

Severity: Warning

Message: Invalid argument supplied for foreach()

Filename: core/MY_Model.php

Line Number: 450

Here is a print_r($result) right after the database query:
Array ( [0] => stdClass Object ([id] => 1 [name] => Group1) [1] => stdClass Object ([id] => 2 [name] => Group2) )

Here is a print_r($result) right after the _run_after_callbacks() function:
id

after_get callback still broken

Hi there

Just downloaded the new copy of the base model and when i add after_get callbacks i get an array with the correct number of elements but each element has no value. I have a table with 1 record and i get the following:
Array
(
[0] =>
)

An table of 3 users gives me this:
Array
(
    [0] => [1] => [2] =>
)
The code for my model looks like this:
http://pastebin.com/rid9UPBp

I did post an issue earlier and you closed it and said that it was fixed in v1.2.0 or something but i downloaded the newest copy and im still having this problem - cheers

-L

PS: I forgot to mention i was using PHP Version 5.3.3-7+squeeze9 on Debian Linux Squeeze

Update_many and delete_many bug

Both are copies from update() and delete() and contain errors.

It is better to implement them like insert_many():

$results = array();

foreach ($primary_values as $primary_value)
{
    $results[] = $this->update($primary_value, $data, $skip_validation);
}

return $results;

Loading the model in subfolders errors

I create a model in subfolder , like users/user_model.php, users/role_model.php, and make a belongsTo relationship between them, when I get a user instance, I could not get the role

Implements a "as_my_object"

Hi,

I've tried to write a "as_my_object" method in "MY_Model" the problem is, I couldn't get the way of accomplishing that . As "my object" I say something as a object of mine, because when I retrieve the db row through the "get()" method always comes as a object of the PHP 'inner' class "stdcass". I would like to retrieve that row as a PHP class of mine own making. I know it's possible to do that because CI native database functions allows you to specify the name of the class you want the row should be a instance of. Read, for example, in the user guide, generating query results.

Thanks for your attention,

Broken after_get

MY_Model::after_get; appears to be broken, if i try to use it i simply get an array of empty objects. That is, the correct amount of objects, but objects with no attributes...

private _return_type()

Shouldn't the _return_type() method be protected instead of private? This would allow you to use it in your own methods when you're extending from the MY_Models class.

insert/update_many not using batch calls

I was browsing the source code and realized that the insert_many and update_many aren't using CodeIgniter's insert_batch and update_batch methods, preferring individual queries (which are slower than batching multiple rows at once).

Is there a reason behind this decision?

dropdown() issues when doing joins via before_get

If I add this code to my Supplier_model

public $before_get = array( 'join_attributes' );

function join_attributes()
{
    $this->db->join('products', 'products.supplier_id = suppliers.id');
}

I get an error

Error Number: 1052
Column 'id' in field list is ambiguous
SELECT `id`, `supplier_name` FROM (`suppliers`) JOIN `products` ON `products`.`supplier_id` = `suppliers`.`id`

I use join_attributes in almost all my models to get all related data back from my queries, but it seems this "kills" the dropdown() function. Is there a way around this?

Proposal for expanding beyond pluralized table names, as well as adding field data support

Not all of my table names are pluralized, for example, sometimes it is agent instead of agents and other variations on that theme.

In those environments, it helps to have a fallback to singular format.

Also, I like to have access to the fields and their type as returned by CodeIgniter. In addition grabbing the field data from
codeigniter gives us a chance to properly guess the primary key field, in case it isn't 'id'

This is what I ended up with:

/**
 * First pulls _m and _model off the name of the class, then pluralizes the result.  If the table
 * doesn't exist, tries the singular version (i.e. if it was named agent_model for table agent,
 * instead of table agents).
 *
 * @return void
 */
private function _fetch_table()
{
    if (($this->_table == NULL) or ($this->_table == 'MY_Model'))
    {
        $class     = preg_replace('/(_m|_model)?$/', '', get_class($this));
        $plural_form = plural(strtolower($class));
        if ($this->db->table_exists($plural_form))
        {
            // pluralized form exists
            $this->_table = $plural_form;
        }
        else
        {
            // fall back to non plural form
            $this->_table = $strtolower($class);
        }
        // load the fields into the $fields array using information from CodeIgniter
        $this->_set_fields();
    }
}

/**
 * Setting the fields pulls the master field information from CI and sets an array of name=>type
 * in case you want to use the type for validation, etc.
 * 
 * It also tries to guess the primary_key by using the primary_key flag in the field_data.
 * @return void
 */
private function _set_fields()
{
    if ($this->db->table_exists($this->_table))
    {
        $table_fields = $this->db->field_data($this->_table);
        foreach ($table_fields as $field)
        {
            if ($field->primary_key == '1')
            {
                $this->primary_key = $field->name;
            }
            $this->fields[$field->name] = $field->type;
        }
    }
}

A proposal for corrections within _run_before_callbacks()

private function _run_before_callbacks($type, $params = array())
{
    $name = 'before_' . $type;
    // 1: Modified by Ivan Tcholakov, 28-APR-2012.
    //$data = (isset($params[0])) ? $params[0] : FALSE;
    $data = (isset($params[0])) ? $params[0] : array();
    //

    if (!empty($this->$name))
    {
        foreach ($this->$name as $method)
        {
            // 2: Modified by Ivan Tcholakov, 28-APR-2012.
            //$data += call_user_func_array(array($this, $method), $params);
            if ($method == 'prepare_data') {
                // See the next proposal #19 - https://github.com/jamierumbelow/codeigniter-base-model/issues/19
                // prepare_data() method is to be called at first place.
                $data = call_user_func_array(array($this, $method), $params);
            } else {
                $data = call_user_func_array(array($this, $method), $params) + $data;
            }
            //

            // 3: Added by Ivan Tcholakov, 28-APR-2012.
            $params[0] = $data;
            //
        }
    }

    return $data;
}

This is hard to be explained (I am not a native English speaker):

1: This is for avoiding a possible case like this one: array('key' => 'value') + FALSE
2: Supporting a chain of callbacks - swapping the order around the sum operator actually updates the processed data.
3: Supporting a chain of callbacks - passing updated data to the next callback.

getter for the primary key name

Hi, Woold be helpful to add getter for the primary key name like this
public function primary_key() { return $this->primary_key; }

composite primary keys?

Hiya. Is there a way to get MY_Model to allow composite primary keys? Currently i'm having to override a bunch of the default functions, and it seems like there ought to be a better way!

A proposal - adding a filter for writable (allowed and existing) fields

Here is a handy way for filtering a record's data before creation or update. Only fields that are defined as "writable" are passed to the query builder. Also, PHP types of the correspondent fields are defined, so generated SQL statement to be precise.

class MY_Model extends CI_Model
{
    ...
    ...
    ...

    // Added by Ivan Tcholakov, 28-APR-2011.
    protected $writable_fields = array();

    ...
    ...
    ...

    // Added by Ivan Tcholakov, 28-APR-2012.
    // Use this method inside "create" and "update" callbacks.
    public function prepare_data($data) {

        $result = array();

        if (is_array($data) && is_array($this->writable_fields)) {

            foreach ($data as $key => & $value) {
                if (isset($this->writable_fields[$key])) {

                    if (!is_null($value)) {

                        switch ($this->writable_fields[$key]) {
                            case 'string':
                                $result[$key] = (string) $value;
                                break;
                            case 'integer':
                            case 'int':
                                $result[$key] = (int) $value;
                                break;
                            case 'float':
                            case 'double':
                                $result[$key] = (float) $value;
                                break;
                            case 'boolean':
                            case 'bool':
                                $result[$key] = (bool) $value ? 1 : 0;  // This is a MySQL-specific implementation.
                                break;
                            default:
                                break;
                        }

                    } else {
                        $result[$key] = null;
                    }
                }
            }
        }

        return $result;
    }

}

Usage example:

<?php if (!defined('BASEPATH')) { exit('No direct script access allowed.'); }

/*
CREATE TABLE `offers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `vendor_id` int(11) NOT NULL,
  `date_start` date NOT NULL,
  `date_end` date DEFAULT NULL,
  `short_description` text NOT NULL,
  `description` text NOT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '0'
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
*/

class offers extends MY_Model {

    // The primary key 'id' is not writable.
    public $writable_fields = array(
        'vendor_id' => 'int',
        'date_start' => 'string',
        'date_end' => 'string',
        'short_description' => 'string',
        'description' => 'string',
        'status' => 'int'
    );

    public $before_create = array('prepare_data');
    public $before_update = array('prepare_data');

    public function __construct() {

        parent::__construct();

        $this->_table = 'offers';

    }

    public function test_create() {
        $data = array(
            'vendor_id' => 1,
            'date_start' => '2012-04-27',
            'date_end' => '2020-04-27',
            'short_description' => 'Test',
            'description' => 'This is a test offer.',
            'status' => 1,
            'garbage_key' => 'garbage_value'   // Data contamination, it is to be filtered.
        );
        $this->insert($data);
    }

}

Fatal Error with active record object?

Jamie, I just ran into a strange issue and am hoping you may have an idea of what's happening–I'm pretty sure that I'm missing something obvious here. I'm using the latest code from master. Seems that all the active record calls are throwing a fatal error:

phpFatal error: Call to a member function where() on a non-object in filepath/MY_Model.php on line 122

These errors also happen on get() and select() at various line numbers, not just 122. If I just extend the CI_Model everything works fine.

Also, I get this error on functions I have written in the model which is extending MY_Model, even when these functions don't use any of the MY_Model functionality... for example:

public function get_user_posts($id = false, $limit = false, $offset = 0) {
            $this->db->select('*');
            $this->db->from('posts');
            $this->db->where('created_by_id', $id);
            $this->db->order_by('created', 'desc');
            if($limit) {
                $this->db->limit($limit, $offset);
            }
            $query = $this->db->get();

            return ($query->num_rows() > 0) ? $query->result() : false;

        }

The above function throws php Fatal error: Call to a member function select() on a non-object even though its only extending MY_Model and not using any of its functions.

Are you somehow modifying things at a core level that may cause this? Any ideas? What am I missing!

Thanks,
-Matt

set callback option from controller

there should be option to set callbacks from controller all before, after variables are protected we can not set them from controller there should be set_callback function to set before or after callbacks with type

$this->child_model->set_callback('before_get','test1'); 

so we will not duplicate model right now if you set any callback for example before_get will be called for all getting records

class child_model extend MY_Model
{

protected $before_get = array('test');

}

or for sprecific get we have to do this

class child_model extend MY_Model
{

function get_all()
{
  $this->before_get = array('test');

 return parent::get_all();
}

function get()
{
  $this->before_get = array('test1');

 return parent::get();
}

}

i did some setting callbacks from controller

http://scrp.at/bz7

looking some more better option

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.