This project is a fork of elegant-orm of nazieb user, the intent of this fork is initially make it 100% compatible with CodeIgniter 3 and MySQL 5.6+. After starts fixes and support for other databases. The project was renamed to become more independent and does not confuse users. The name was based on the speed and lightness of a rabbit.
I work with Java for many years, and perhaps for this reason, has had the idea of using annotation in this project. Annotations or other object metadata are deeply missed in PHP, but thinking well and performing tests is not a good idea to simulate annotations in comments. Open a php file and parse the code to retrieve the annotations takes time and processing power. I decided to use constants to define each attribute and One constant for the definition of the entity. They will have the suffix definition
with JSON code.
PS: For this particular use, constants will be implement in camelcase convention, we will not follow the convention and set all characters in uppercase because it will be used for another purpose.
Ex:
class User extends RabbitORM\Model {
const userDefinition = '{"name": "Users", "table": "users"}';
private $idUser;
const idUserDefinition = '{"name":"idUser","column":"id_user","primaryKey":"true"}';
private $FirstName;
const firstNameDefinition = '{"name":"firstName", "column":"first_name"}';
public getIdUser()...
}
I strongly encourage, although not mandatory, encapsulate the attributes. It will be very useful to work with related entities.
ORM for CodeIgniter based on Laravel's Eloquent. The Rabbit ORM brings the beauty and simplicity of working with Eloquent ORM in Laravel to CodeIgniter framework.
Download last release https://github.com/fabiocmazzo/rabbit-orm/releases
Extract to your application/libraries directory In your config/autoload.php file, add elegant-orm/elegant to $autoload['libraries']. So it will look like this:
$autoload['libraries'] = array('rabbit-orm/Rabbitorm');
Differently from our predecessor, models in CodeIgniter are preserved. Entities shall be saved in application/entities
.
Example: Entity for table user, located in entities/user.php
class User extends RabbitORM\Model {
const userDefinition = '{"name": "Users", "table": "users"}';
private $idUser;
const idUserDefinition = '{"name":"idUser", "column":"id_user","primaryKey":"true"}';
private $FirstName;
const firstNameDefinition = '{"name":"firstName","column":"first_name"}';
public getIdUser()...
}
In current source code you must define Column for every property, this is required, unmapped fields will not be returned. This choice was made to become clearer definition of the entity in class, not bring unnecessary data from database and reduce the coupling to the database.
Here are some properties you can use to customize the model
$table
: to define the table name. This property is mandatory to set$db_group
: to define which database group the model will connect. The groups can be found inconfig/database.php
. By default it uses "default" group$incrementing
: to define whether your PK is auto-increment. Default value istrue
. If you'd like to generate your Primary Key value by custom function, set this tofalse
$users = User::all();
foreach($users as $user)
{
echo $user->name;
}
$user = User::find(1);
echo $user->name;
You can also pass multiple IDs or an array to get multiple records.
$users = User::find(1, 2, 3);
// or
$users = User::find( array(1, 2, 3) );
You can still use Code Igniter Active Record to generate a custom query.
$users = User::where('status', 1)->get();
foreach($users as $user) echo $user->name;
Or if you only want to retrieve the first record, you can use first()
method
$user = User::where('status', 1)->first();
echo $user->name;
Plucking is the way to retrieve a single column value of the first record.
// Returns the `name` column of the first row
echo 'Newest user is: ' . User::order_by('id', 'desc')->pluck('name');
By default Rabbit will generate a SELECT *
query for all examples above. If you think this is a bad practice, you can select only specific columns you need in several ways:
User::all( array('id', 'username') );
User::where('status', 1)->get( array('id', 'username') );
User::select( array('id', 'username') )->get();
User::where('status', 1)->first( array('id', 'username') );
Note: for now find()
method doesn't support selecting specific column.
Rabbit also provides aggregates method, such as max
, min
, avg
, sum
, and count
. You can call these methods right away or chain them with some CI Active Records method.
$total = User::count();
$max = Product::max('price');
// Example of chaining with where()
$min = Product::where('category', 1)->min('price');
$avg = Product::avg('price');
// Another chaining example
$sum = Order::where('status', 1)->sum('price');
$user = new User;
$user->name = 'John Doe';
$user->email = 'dummy@example.com';
$user->save();
After saving the record, if your model uses auto increment primary key, the generated insert id will be set to the object. So if you use example above, you can show the new user's ID right away
echo "New User ID: " . $user->id;
Note that the property isn't always id
. It depends on the primary
property you've set before.
Alternatively, you can use create
method to create new models.
$user = User::create( array('name' => 'John Doe', 'email' => 'dummy@example.com') );
// create() method will return a newly created model or false if inserting fails
echo $user->id;
$user = User::find(1);
$user->name = 'Jack Doe';
$user->save();
You still can use CodeIgniter Active Record to generate a custom query before updating.
User::where('status', 1)->update( array('name' => 'Jane Doe') );
Or alternatively you can call update
method right away.
User::update( array('name' => 'Jane Doe'), array('id' => 1) );
// The first parameter is the new data, and the second is the "where" condition
There are several ways to delete model
// Delete retrieved model
$user = User::find(1);
$user->delete();
// Delete a single model by its primary key
User::delete(1);
// Delete multiple models by their primary keys
User::delete(1, 2, 3);
//or
User::delete( array(1, 2, 3) );
// Use Active Record
User::where('status', 1)->delete();
Scopes is a custom function you can create in your models to generate custom queries
Some conventions:
- Scope method name must be in camel case
- Scope method name must be start with
scope
- At least one parameter is required. This first parameter is a
QueryBuilder
which you can use to call Active Record methods
class User extends Rabbit\Model {
protected $table = "user";
function scopeActive($query)
{
return $query->where('status', 1)->order_by('name');
}
}
Using example above, you can do this in your controller
$active_users = User::active()->get();
Note that the method name isn't using scope
prefix.
Scopes can also accept parameters to be used in generating queries.
class User extends Rabbit\Model {
protected $table = "user";
// Search an active user by name
function scopeSearch($query, $keyword)
{
return $query->like('name', $keyword)->where('status', 1);
}
}
// In your controller
$search_results = User::search('John')->get();
This the example how to define a one-to-one relationship between a User
model with Phone
. In this case, a User
might have one Phone
class User extends Rabbit\Model {
protected $table = "user";
function phone()
{
return $this->hasOne('Phone');
}
}
The parameter to hasOne
method is the name of the related model. Once the relationship set you can retrieve it in you controller this way:
$user = User::find(1);
$phone = $user->phone;
// You can work with $phone object like the usual way
// Returns its property
echo $phone->brand;
// Or updates it
$phone->brand = "Samsung";
$phone->save();
Note: the name of the method where you call the hasOne
isn't have to be the same with the related model name. You can name it anything you want, just make sure it doesn't conflict with exisiting table field name.
In the example above the foreign key in phone
table is assumed to be user_id
(lowercased related model's name with _id
suffix). You can define custom key name as second parameter if your foreign key doesn't match this convention.
$this->hasOne('Phone', 'custom field name');
You can also define the inverse of the relationship. For the example after you get a Phone object, you want to know who is the name owner. In the Phone
model you have to call the belongsTo
method.
class Phone extends Rabbit\Model {
protected $table = "phone";
function owner()
{
return $this->belongsTo('User');
}
}
// In your controller:
$phone = Phone::find(1);
echo $phone->owner->name;
You can also define a custom foreign key as second parameter to belongsTo
method.
An example of one to many relationship is an article can has one or many comments. To define such relationship, you can do this:
class Article extends Rabbit\Model {
protected $table = "article";
function comments()
{
return $this->hasMany('Comment');
}
}
The difference between hasOne
and hasMany
is the hasMany
will return an array of matched models, while hasOne
will return only one model. So in your controller, you should use like this:
$article = Article::find(1);
foreach($article->comments() as $comment)
{
echo $comment->text;
}
As in one to one relationship you can define the inverse relationship between Comment
and Article
model.
class Comment extends Rabbit\Model {
protected $table = "comment";
function article()
{
return $this->belongsTo('Article');
}
}
// In controllers
$comment = Comment::find(1);
return $comment->article->title;
Again, you can set a custom foreign key to belongsTo
method as stated in the One to One section.
Many to many relationship is the most complex relationship. It requires a pivot table to bridge the relation between two models.
An example of this relationship is between an Article
model with a Tag
model. An article might has one or more tag while a tag can also be in one on more article.
To define the relationship, you should do this:
class Article extends RabbitORM\Model {
protected $table = "article";
function tags()
{
return $this->belongsToMany('Tag');
}
}
To retrieve the tags:
$article = Article::find(1);
foreach($article->tags as $tag)
{
echo $tag->name;
}
Or you can do vice-versa in Tag
model:
class Tag extends RabbitORM\Model {
protected $table = "tag";
function articles()
{
return $this->belongsToMany('Article');
}
}
// In controllers
$tag = Tag::find(1);
foreach($tag->articles as $article)
{
echo $article->title;
}
By default RabbitORM will assume that the name of pivot table is the concatenated name of two models using underscore in alphabetical order. So if the models are Article
and Tag
, the default pivot table name is article_tag
.
If you want to use another name for the pivot table you can specify in second parameter of the belongsToMany
method.
// Use a custom pivot table name
$this->belongsToMany('Tag', 'custom pivot name');
You can also customize the name of associated keys in the pivot. By default it uses the same convention as in One to One or One to Many. So for the example, in article_tag
table, the fields will be article_id
and tag_id
.
To customize the key name, you can pass third and/or fourth parameter. The third parameter is the associated key of current model, while the fourth is for the related model.
Basicly, this is a belongsToMany
will look like:
$this->belongsToMany('Tag', 'article_tag', 'article_id', 'tag_id');
Let's say an Article
model might have many Comment
. But when you querying an article, probably you want to show the approved comments only, which in this case have status
equals to 1.
You can do it in two ways. The first one is by chaining the relation object with where
method (or any CodeIgniter Active Record class).
// Using example above
$article = Article::find(1);
$comments = $article->comments()->where('status', 1)->get();
foreach($comments as $comment)
{
echo $comment->text;
}
Note that you need to call the comments
as a method ($article->comments()
instead of $article->comments
). And you will always need to call the get()
method at the end to finally fetch the records you want.
The second way is by chaining the hasMany
or belongsToMany
method right after you define the relationship.
class Article extends RabbitORM\Model {
protected $table = "article";
function approvedComments()
{
return $this->hasMany('Comment')->where('status', 1);
}
}
This way you can have a cleaner syntax without needs to call get()
method. In your controllers you can do this:
$article = Article::find(1);
foreach($article->approvedComments as $comment)
{
echo $comment->text;
}
You can call the approvedComments
as a property to do the loop like example above, or you call it as a method to chain it again with another Active Record method. Just don't forget to call get()
at the end.
// Show only 5 first comments
$comments = $article->approvedComments()->limit(5)->get();
// Or sort the comments by date
$comments = $article->approvedComments()->order_by('date', 'desc')->get();
Eager loading is a technique to reduce the number of queries needed to relate one model to another. Now take a look at this model:
class Article extends RabbitORM\Model {
protected $table = "article";
function comments()
{
return $this->hasMany('Comment');
}
}
Then, for the example we want to show all articles along with their comments. So perhaps you will use this code:
$articles = Article::all();
foreach($articles as $article)
{
echo $article->title;
foreach($article->comments() as $comment)
echo $comment->text;
}
There's nothing wrong with that code, except that each time you call the relationship method, (the comments()
method) to retrieve the comments a new query is built. So imagine if we have 50 articles to be shown, that means the loop will run 51 queries (the 1 is for fetching all articles) like this:
SELECT * FROM article;
SELECT * FROM comment WHERE article_id = 1;
SELECT * FROM comment WHERE article_id = 2;
SELECT * FROM comment WHERE article_id = 3;
...
To solve that problem, RabbitORM provides the support for eager loading. Now take a look at this code
$articles = Article::all();
$articles->load('comments');
foreach($articles as $article)
{
echo $article->title;
foreach($articles->comments as $comment)
echo $comment->text;
}
It will produce the same output, but with a drastically decrease in the number of queries. Instead of running N + 1 queries like example above, it will run queries like this:
SELECT * FROM article;
SELECT * FROM comment WHERE article_id IN (1, 2 ,3 ,4 ,5 .....);
The secret is the load()
method. Pass the name of the relation method (comments
) then it will smartly build the query using IN
keyword and match each comment to the right article. Note that when fetching the comments using eager load, you should always call it as a property instead of as a method as in the example above ($articles->comments
, not $articles->comments()
).
Sometimes you want to transform some of your model's value when setting or getting them. The accessors will transform your model value before returning it to your application, so you don't have to do the transformation by yourself each time you need that value. While the mutators will transform it before you save it to the database, freeing yourself from doing it every time you gonna insert a new row.
Example of accessors:
class User extends RabbitORM\Model {
protected $table = "user";
function getAttrFirstName($value)
{
return strtoupper($value);
}
}
That way, in your controllers every time you echo or use the first_name
field from a User model, it will always in all capital letter despite the actual value saved.
$user = User::find(1);
echo $user->first_name; // will echo something like 'JOHN'
Example of mutators:
class User extends RabbitORM\Model {
protected $table = "user";
function setAttrFirstName($value)
{
return strtolower($value);
}
}
So when you save a new User or updating an existing one's first name, the value will be transformed into lowercase.
$user = new User;
$user->first_name = 'JOHN';
$user->save();
In example above the value will be lowercased upon saving, so the in database the first_name value will be: john
When you want to declare a mutator/accessor method, here is some rules you need to understand:
- The methods name needs to in camelCase
- Accessors should be prefixed with
getAttr
while mutators withsetAttr
like in the example above - Accessors and mutators actually receive two parameters. The first is the value of the field you want to transform, and the second is the model object (optional) in case you want to look for some reference to other field.
Example:
class User extends RabbitORM\Model {
protected $table = "user";
function getAttrLastName($value, $model)
{
// look up to "gender" field to define salutation to be attached
$salutation = $model->gender == 'male' ? 'Mr.' : 'Mrs.';
return $salutation . ' ' . ucwords($value);
}
}
- You may define an accessor for a field that actually doesn't exists in database. For the example in your table you only have
first_name
andlast_name
field. If you want to show a full name, instead of echoing the fields one by one you can do this:
function getAttrFullName($value, $model)
{
// $value will be empty since the field doesn't exist.
// use another field instead
return $model->first_name .' '. $model->last_name;
}
The RabbitORM query result is always returned as a special RabbitORM\Result
object. If you wish to work with plain array instead, you may convert is using toArray()
method.
$users = User::all();
return $users->toArray()
If you want to include a related model, you can use the eager load method
$articles = Article::all();
articles->load('comments');
return $articles->toArray();
You can also convert the models to JSON object instead. Especially if you work with Javascript library like Backbone.js. To do so you can call json()
method
$articles = Article::all();
articles->load('comments');
echo $articles->json();
If you want to debug your application using CI Profiler class, you should define a constant named RABBITORM_DEBUG
with value true
in config/constants.php
file. Otherwise the queries will not show up in the profiling result.
define('RABBITORM_DEBUG', true);