Skip to content

Commit 318cdcc

Browse files
committed
Add ORM relationship
1 parent 89ee90d commit 318cdcc

File tree

2 files changed

+214
-15
lines changed

2 files changed

+214
-15
lines changed

README.md

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This ORM Model extension is collected into [yidas/codeigniter-pack](https://gith
1616
FEATURES
1717
--------
1818

19-
- ***ORM** Model with **Elegant patterns** as Laravel Eloquent ORM & Yii2 Active Record*
19+
- ***[ORM](#active-record-orm)** Model with **Elegant patterns** as Laravel Eloquent ORM & Yii2 Active Record*
2020

2121
- ***[CodeIgniter Query Builder](#find)** integration*
2222

@@ -58,12 +58,15 @@ OUTLINE
5858
- [Updates](#updates)
5959
- [Deletes](#deletes)
6060
- [Accessing Data](#accessing-data)
61+
- [Relationships](#relationships)
6162
- [Methods](#methods-1)
6263
- [findone()](#findone)
6364
- [findAll()](#findall)
6465
- [save()](#save)
6566
- [beforeSave()](#beforesave)
6667
- [afterSave()](#afterave)
68+
- [hasOne()](#hasone)
69+
- [hasMany()](#hasmany)
6770
- [toArray()](#toarray)
6871
- [Soft Deleted](#soft-deleted)
6972
- [Configuration](#configuration-1)
@@ -638,6 +641,43 @@ $title = $post->title;
638641
$subtitle = $post['subtitle'];
639642
```
640643

644+
### Relationships
645+
646+
Database tables are often related to one another. For example, a blog post may have many comments, or an order could be related to the user who placed it. This library makes managing and working with these relationships easy, and supports different types of relationships:
647+
648+
- [One To One](#hasone)
649+
- [One To Many](#hasmany)
650+
651+
To work with relational data using Active Record, you first need to declare relations in models. The task is as simple as declaring a `relation method` for every interested relation, like the following,
652+
653+
```php
654+
class CustomersModel extends yidas\Model
655+
{
656+
// ...
657+
658+
public function orders()
659+
{
660+
return $this->hasMany('OrdersModel', ['customer_id' => 'id']);
661+
}
662+
}
663+
```
664+
665+
Once the relationship is defined, we may retrieve the related record using dynamic properties. Dynamic properties allow you to access relationship methods as if they were properties defined on the model:
666+
667+
```php
668+
$orders = $this->CustomersModel->findOne(1)->orders;
669+
```
670+
671+
> The dynamic properties' names are same as methods' names, like [Laravel Eloquent](https://laravel.com/docs/5.7/eloquent-relationships)
672+
673+
For **Querying Relations**, You may query the `orders` relationship and add additional constraints with CI Query Builder to the relationship like so:
674+
675+
```php
676+
$customer = $this->CustomersModel->findOne(1)
677+
678+
$orders = $customer->orders()->where('active', 1)->get()->result_array();
679+
```
680+
641681
### Methods
642682

643683
#### `findOne()`
@@ -721,6 +761,70 @@ This method is called at the end of inserting or updating a active record
721761
public boolean beforeSave(boolean $insert, array $changedAttributes)
722762
```
723763

764+
#### `hasOne()`
765+
766+
Declares a has-one relation
767+
768+
769+
```php
770+
public CI_DB_query_builder hasOne(string $modelName, string $foreignKey=null, string $localKey=null)
771+
```
772+
773+
*Example:*
774+
```php
775+
class OrdersModel extends yidas\Model
776+
{
777+
// ...
778+
779+
public function customer()
780+
{
781+
return $this->hasOne('CustomersModel', 'id', 'customer_id');
782+
}
783+
}
784+
```
785+
*Accessing Relational Data:*
786+
```php
787+
$this->load->model('OrdersModel');
788+
// SELECT * FROM `orders` WHERE `id` = 321
789+
$order = $this->OrdersModel->findOne(321);
790+
791+
// SELECT * FROM `customers` WHERE `is` = 321
792+
// $customer is a Customers active record
793+
$customer = $order->customer;
794+
```
795+
796+
#### `hasMany()`
797+
798+
Declares a has-many relation
799+
800+
801+
```php
802+
public CI_DB_query_builder hasMany(string $modelName, string $foreignKey=null, string $localKey=null)
803+
```
804+
805+
*Example:*
806+
```php
807+
class CustomersModel extends yidas\Model
808+
{
809+
// ...
810+
811+
public function orders()
812+
{
813+
return $this->hasMany('OrdersModel', 'customer_id', 'id');
814+
}
815+
}
816+
```
817+
*Accessing Relational Data:*
818+
```php
819+
$this->load->model('CustomersModel');
820+
// SELECT * FROM `customers` WHERE `id` = 123
821+
$customer = $this->CustomersModel->findOne(123);
822+
823+
// SELECT * FROM `order` WHERE `customer_id` = 123
824+
// $orders is an array of Orders active records
825+
$orders = $customer->orders;
826+
```
827+
724828
#### `toArray()`
725829

726830
Active Record transform to array record

src/Model.php

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,7 @@ public function findOne($condition=[])
525525
return $record;
526526
}
527527

528-
// ORM handling
529-
$this->_readProperties = $record;
530-
// Primary key condition to ensure single query result
531-
$this->_selfCondition = $record[$this->primaryKey];
532-
533-
return $this;
528+
return $this->createActiveRecord($record, $record[$this->primaryKey]);
534529
}
535530

536531
/**
@@ -562,14 +557,8 @@ public function findAll($condition=[])
562557
if (!isset($record[$this->primaryKey])) {
563558
throw new Exception("Model's primary key not set", 500);
564559
}
565-
// Create an ActiveRecord
566-
$activeRecord = new static();
567-
// ORM handling
568-
$activeRecord->_readProperties = $record;
569-
// Primary key condition to ensure single query result
570-
$activeRecord->_selfCondition = $record[$this->primaryKey];
571-
// Collect
572-
$set[] = $activeRecord;
560+
// Create an ActiveRecord into collect
561+
$set[] = $this->createActiveRecord($record, $record[$this->primaryKey]);
573562
}
574563

575564
return $set;
@@ -987,6 +976,24 @@ public function withAll()
987976
return $this;
988977
}
989978

979+
/**
980+
* New a Active Record from Model by data
981+
*
982+
* @param array $readProperties
983+
* @param array $selfCondition
984+
* @return object ActiveRecord(Model)
985+
*/
986+
public function createActiveRecord($readProperties, $selfCondition)
987+
{
988+
$activeRecord = new static();
989+
// ORM handling
990+
$activeRecord->_readProperties = $readProperties;
991+
// Primary key condition to ensure single query result
992+
$activeRecord->_selfCondition = $selfCondition;
993+
994+
return $activeRecord;
995+
}
996+
990997
/**
991998
* Active Record (ORM) save for insert or update
992999
*
@@ -1074,6 +1081,67 @@ public function afterSave($insert, $changedAttributes)
10741081
// overriding
10751082
}
10761083

1084+
/**
1085+
* Declares a has-many relation.
1086+
*
1087+
* @param string $modelName The model class name of the related record
1088+
* @param string $foreignKey
1089+
* @param string $localKey
1090+
* @return object CI_DB_query_builder
1091+
*/
1092+
public function hasMany($modelName, $foreignKey=null, $localKey=null)
1093+
{
1094+
return $this->_relationship($modelName, __FUNCTION__, $foreignKey, $localKey);
1095+
}
1096+
1097+
/**
1098+
* Declares a has-many relation.
1099+
*
1100+
* @param string $modelName The model class name of the related record
1101+
* @param string $foreignKey
1102+
* @param string $localKey
1103+
* @return object CI_DB_query_builder
1104+
*/
1105+
public function hasOne($modelName, $foreignKey=null, $localKey=null)
1106+
{
1107+
return $this->_relationship($modelName, __FUNCTION__, $foreignKey, $localKey);
1108+
}
1109+
1110+
/**
1111+
* Base relationship.
1112+
*
1113+
* @param string $modelName The model class name of the related record
1114+
* @param string $relationship
1115+
* @param string $foreignKey
1116+
* @param string $localKey
1117+
* @return object CI_DB_query_builder
1118+
*/
1119+
protected function _relationship($modelName, $relationship, $foreignKey=null, $localKey=null)
1120+
{
1121+
$this->load->model($modelName);
1122+
1123+
$libClass = __CLASS__;
1124+
1125+
// Check if is using same library
1126+
if (!is_subclass_of($this->$modelName, $libClass)) {
1127+
throw new Exception("Model `{$modelName}` does not extend {$libClass}", 500);
1128+
}
1129+
1130+
// Keys
1131+
$foreignKey = ($foreignKey) ? $foreignKey : $this->primaryKey;
1132+
$localKey = ($localKey) ? $localKey : $this->primaryKey;
1133+
1134+
$query = $this->$modelName->find()
1135+
->where($foreignKey, $this->$localKey);
1136+
1137+
// Inject Model name into query builder for ORM relationships
1138+
$query->modelName = $modelName;
1139+
// Inject relationship type into query builder for ORM relationships
1140+
$query->relationship = $relationship;
1141+
1142+
return $query;
1143+
}
1144+
10771145
/**
10781146
* Active Record transform to array record
10791147
*
@@ -1410,6 +1478,33 @@ public function __get($name)
14101478

14111479
return $this->_readProperties[$name];
14121480
}
1481+
// ORM relationship check
1482+
else if (method_exists($this, $method = $name)) {
1483+
1484+
$query = call_user_func_array([$this, $method], []);
1485+
1486+
// Extract query builder injection property
1487+
$modelName = isset($query->modelName) ? $query->modelName : null;
1488+
$relationship = isset($query->relationship) ? $query->relationship : null;
1489+
1490+
if (!$modelName || !$relationship) {
1491+
throw new Exception("ORM relationships error", 500);
1492+
}
1493+
1494+
// Check return type
1495+
if ($relationship == 'hasOne') {
1496+
1497+
// Keep same query builder from hasOne()
1498+
return $this->$modelName->findOne(null);
1499+
1500+
} else {
1501+
1502+
// Keep same query builder from hasMany()
1503+
return $this->$modelName->findAll(null);
1504+
}
1505+
1506+
}
1507+
// ORM schema check
14131508
else {
14141509

14151510
$class = get_class($this);

0 commit comments

Comments
 (0)