diff --git a/Base_model.php b/Base_model.php index 3210001..51d95d9 100644 --- a/Base_model.php +++ b/Base_model.php @@ -149,7 +149,6 @@ public function __construct(){ * @return mixed */ public function __call($method, $args){ - // ToDo what if the object is already created // create a Queryset if method is class and is present in Queryset if (!method_exists($this, $method) && @@ -287,6 +286,22 @@ public function queryset(){ return $this->_get_queryset(); } + /** + * Returns the name of the primary key column + * @return string + */ + public function primary_key(){ + $table_columns = $this->meta(); + + $primary_key_column = NULL; + foreach ($table_columns as $col) : + if($col->primary_key): + $primary_key_column = $col->name; + endif; + endforeach; + + return $primary_key_column; + } @@ -397,28 +412,6 @@ public function set_dates(){ } } - /** - * ToDo remove from base model - * @ignore - * @param bool $owner_field - * @return bool - */ - public function is_owner($owner_field=FALSE){ - $status = FALSE; - $user_id = $this->auth->user->id; - - if($owner_field==False && $this->user_id == $user_id){ - $status = TRUE; - } - - if($owner_field!=False && $this->{$owner_field} == $user_id){ - $status = TRUE; - } - - return $status; - } - - diff --git a/OrmErrors.php b/OrmErrors.php index 73049e8..bb6ea54 100644 --- a/OrmErrors.php +++ b/OrmErrors.php @@ -10,4 +10,8 @@ */ class OrmErrors extends ErrorException{} +/** + * Raised by Orm + * Class TypeError + */ class TypeError extends OrmErrors{} \ No newline at end of file diff --git a/Queryset.php b/Queryset.php index bd40812..eb0b549 100644 --- a/Queryset.php +++ b/Queryset.php @@ -139,10 +139,10 @@ * The method tells the orm to eagerly load the article and authors in one go when the Queryset is being evaluated. * which will result in two sql queries as shown below: * - *
$articles = $this->with('author')->article_model->all()
+ * $articles = $this->with(['author' =>'author'])->article_model->all()
*
* foreach($articles as $article){
- * $article->related_one('author')->name;
+ * $article->author->name;
* }
*
* // one to fetch all the articles
@@ -619,10 +619,10 @@ public function filter($where, $foreign_key=NULL){
$related_model_name = preg_split("/::/", $key)[0];
$related_model_search_key = preg_split("/::/", $key)[1];
- $related_where[$related_model_search_key] = $value;
+ $related_where[$related_model_search_key] = (is_object($value))?$this->_object_pk_value($value):$value;
else:
- $where_condition[$key] = $value;
+ $where_condition[$key] = (is_object($value))?$this->_object_pk_value($value):$value;
endif;
endforeach;
@@ -654,7 +654,6 @@ public function filter($where, $foreign_key=NULL){
// foreign key table
$foreign_key_table = $this->_search_foreignkey($related_model_name, $foreign_key);
-
// ************************* Try Many To One and One To Many **************************
// if the foreign_key_table is not the current table
@@ -733,6 +732,58 @@ public function distinct(){
return $this;
}
+ /**
+ * Returns the Queryset with it objects grouped by the criteria provided.
+ *
+ * USAGE:
+ *
+ * To group countries by year of independence.
+ *
+ * $this->countries->all()->group_by(['independence_year']);
+ *
+ * @param array $criteria a list of fields to group the objects on.
+ * @return $this
+ * @throws OrmExceptions
+ */
+ public function group_by($criteria=[]){
+ if($this->_evaluated){
+ throw new OrmExceptions(sprintf("group_by() cannot be called on an evaluated Queryset"));
+ }
+ if(!is_array($criteria)){
+ throw new OrmExceptions(sprintf("group_by() expects an array"));
+ }
+
+ $this->_database->group_by($criteria);
+
+ return $this;
+ }
+
+ /**
+ * To limit filter down on the results of a group_by based on some criteria.
+ *
+ * USAGE:
+ *
+ * To group students having a balance of 2000 based on year of admission .
+ *
+ * $this->students->all()->group_by(['admission_year'])->having(['bal__gt'=>2000]);
+ *
+ * @param array $criteria
+ * @return $this
+ * @throws OrmExceptions
+ */
+ public function having($criteria=[]){
+ if($this->_evaluated){
+ throw new OrmExceptions(sprintf("having() cannot be called on an evaluated Queryset"));
+ }
+ if(!is_array($criteria)){
+ throw new OrmExceptions(sprintf("having() expects an array"));
+ }
+
+ $this->db->having($criteria);
+
+ return $this;
+ }
+
/**
* Returns the related data, this can return one object or an array of records.
*
@@ -823,21 +874,24 @@ public function related_one($model_name){
*
* To Eager load related items of a single item
*
- * $category = $this->category_model->with("product_model")->get(array("id"=>1));
+ * Provide the name on which the eagerly loaded results will be accessed from in the example below all products of
+ * category with the `id = 1` will be accessed from the variable `products` .
*
- * $category->product_models; // the products are now accessible like so
+ * $category = $this->category_model->with(["products"=>"product_model"])->get(array("id"=>1));
*
- * foreach($rr->product_models as $product){
+ * $category->products; // the products are now accessible like so
+ *
+ * foreach($rr->products as $product){
* echo $product->name;
* }
*
* To Eager Many To Many Relationship
*
- * $roles = $this->role->with('permission_model')->filter(array('user_model::id'=>1));
+ * $roles = $this->role->with(['permissions'=>'permission_model'])->filter(array('user_model::id'=>1));
* foreach ($roles as $role) {
* echo $role ;
*
- * foreach ($role->permission_models as $perm) {
+ * foreach ($role->permissions as $perm) {
* echo $perm->name;
* }
* }
@@ -845,24 +899,35 @@ public function related_one($model_name){
*
* To Eager Load more than one relationship , this eager loads a user roles and products.
*
- * $usr= $this->user_model->with(["products", 'role'])->get(["id"=>1]);
+ * $usr= $this->user_model->with(["products"=>"product_model", 'roles'=>"role_model"])->get(["id"=>1]);
*
*
*
*
* @param string|array $eager_model_name the name/names of models to eager load.
* @return Queryset
+ * @throws OrmExceptions
*/
public function with($eager_model_name){
$this->_eager_load = TRUE;
- if(is_string($eager_model_name)):
- $this->_models_to_eager_load[] = $eager_model_name;
+ if(!is_array($eager_model_name)):
+ throw new OrmExceptions("with() expected an array");
endif;
+ // ensure that name to mount eager models on is set
+ foreach ($eager_model_name as $key=>$name) :
+ if(is_numeric($key)):
+ throw new OrmExceptions(sprintf("with() requires a name on which to store `%s` objects", $name));
+ endif;
+ endforeach;
+
+
if(is_array($eager_model_name)):
$this->_models_to_eager_load = $eager_model_name;
endif;
+
+
return $this;
}
@@ -883,7 +948,6 @@ public function value(){
}
-
/**
* Shows the `sql` statement to be executed at certain point of the queryset chaining.
*
@@ -1239,8 +1303,6 @@ public function remove($related=[]){
$this->_database->where_in($related_model_short_name."_".$related_pk, $related_ids);
$this->_database->delete($join_table);
- var_dump($this->_database->last_query());
-
}
/**
@@ -1460,7 +1522,7 @@ protected function _cast_to_model($fetch_result, $model_name=NULL){
// get data from the table rows
foreach ($fetch_result as $row_obj) {
$class_instance = $this->_clone_model($model_name);
- // ToDo its doing multiple sql calls because we are getting the meta data of the table with each initialization of an model object
+
foreach ($row_obj as $column=>$value) {
$class_instance->{$column} = $value;
@@ -1513,7 +1575,7 @@ public function _eager_loader(){
// if is array
if(is_array($this->_query_result)){
foreach ($this->_query_result as $row) {
- $pk = $this->_related_model_primary_key(get_class($row));
+ $pk = $row->primary_key();
$primary_result_ids[] = $row->{$pk};
}
@@ -1522,7 +1584,7 @@ public function _eager_loader(){
// if primary result was an object
if(is_object($this->_query_result)){
// get primary key
- $pk = $this->_related_model_primary_key(get_class($this->_query_result));
+ $pk = $this->_query_result->primary_key();
$primary_result_ids[] = $this->_query_result->{$pk};
}
@@ -1541,63 +1603,99 @@ public function _eager_loader(){
}
// loop through all the models we area eager loading
- foreach ($this->_models_to_eager_load as $eager_load_model_name) {
+ foreach ($this->_models_to_eager_load as $mount_point=>$eager_load_model_name) {
+ $foreign_key = NULL;
+ // look for relationship
+ if(preg_match("/::/", $eager_load_model_name)):
+ $foreign_key = preg_split("/::/", $eager_load_model_name)[1];
+ $eager_load_model_name = preg_split("/::/", $eager_load_model_name)[0];
+ endif;
// load the model
$this->_load_related_model($eager_load_model_name);
+ // table names
+ $eager_table_name = $this->_context->{$eager_load_model_name}->table_name();
+ $eager_table_short_name = $this->_related_table_short_name($eager_load_model_name);
// get the table with the foreign key
- if($this->_search_foreignkey($eager_load_model_name)):
+ if($foreign_key_table = $this->_search_foreignkey($eager_load_model_name, $foreign_key)):
- // create a new Quuerset, based on the model to eager load
+ // create a new Querset, based on the model to eager load
$q = new Queryset($this->_context->{$eager_load_model_name});
// perform a where in on the eager model using the primary result ids
- $current_pk = $this->_current_model_primary_key();
+ $primary_result_pk = $this->_current_model_primary_key();
$args = array(
- get_class($this->_context)."::".$current_pk."__in" => $primary_result_ids
+ get_class($this->_context)."::".$primary_result_pk."__in" => $primary_result_ids
);
- $eager_models = $q->distinct()->filter($args);
+ $eager_models = $q->distinct()->filter($args, $foreign_key);
+
// if primary result was an array
// mount the eagerly loaded data on the primary results
if(is_array($primary_results)):
foreach ($primary_results as $primary_result):
- // creates the eagerly loaded data class variable
- if(!property_exists($primary_result, $eager_load_model_name."s")):
- $primary_result->{$eager_load_model_name."s"} = array();
+
+ // creates the eagerly loaded data mount point on the primary results
+ if(!property_exists($primary_result, $mount_point)):
+ $primary_result->{$mount_point} = NULL;
endif;
- // works for one to many
- foreach ($eager_models as $model_eager) :
+ // ***************************** Many to One side Eager Loading ****************************
+ // load data on mount point
+ // in product has one category, the product is the primary result here
+ if($foreign_key_table === $primary_result->table_name()):
+ foreach ($eager_models as $eager_model) :
+ $eager_pk = $eager_model->primary_key();
+ $foreign_key = ($foreign_key)? $foreign_key : $eager_table_short_name.'_'.$eager_pk;
+
+ if($primary_result->{$foreign_key}===$eager_model->{$eager_pk}):
+ $primary_result->{$mount_point}=$eager_model;
+ endif;
+ endforeach;
+ endif;
- if($model_eager->{$primary_result_table_name.'_'.$current_pk}===$primary_result->{$current_pk}):
- $primary_result->{$eager_load_model_name."s"}[]=$model_eager;
- endif;
+ // ************************ One to Many side Eager Loading ****************************
+ // in category has many products
+ // works for one to many i.e. category is the primary result
+ if($foreign_key_table !== $primary_result->table_name()):
+ $primary_result->{$mount_point} = [];
+ foreach ($eager_models as $model_eager) :
+ $foreign_key = ($foreign_key)? $foreign_key : $primary_result_table_name.'_'.$primary_result_pk;
- endforeach;
+ if($model_eager->{$foreign_key}===$primary_result->{$primary_result_pk}):
+ $primary_result->{$mount_point}[]=$model_eager;
+ endif;
+
+ endforeach;
+ endif;
endforeach;
endif;
else:
- // Many to Many Eager Loading
+ // ***************************************** Many to Many Eager Loading *********************
$join_table = $this->_get_join_table($eager_load_model_name);
$current_short_name = $this->_current_table_short_name();
$current_pk = $this->_current_model_primary_key();
- $select = $this->_context->{$eager_load_model_name}->table_name().".* ,$join_table.$current_short_name"."_".$current_pk;
+ $select = $eager_table_name.".* ,$join_table.$current_short_name"."_".$current_pk;
$this->_database->select($select);
$this->_database->from($this->_context->table_name());
- $this->_m2m_join($eager_load_model_name);
- $this->_database->where_in($this->_context->table_name().'.'.$current_pk, $primary_result_ids);
+
+ // create the joins
+ $this->_m2m_join($eager_load_model_name, NULL, $foreign_key);
+
+ // the where clause
+ $this->__where_clause(get_class($this->_context), [$current_pk."__in"=>$primary_result_ids]);
$query = $this->_database->get();
+ // convert results into there respective models
$result_cast = $this->_cast_to_model($query->result(), $eager_load_model_name);
@@ -1607,15 +1705,15 @@ public function _eager_loader(){
foreach ($primary_results as $primary_result):
// creates the eagerly loaded data class variable
- if(!property_exists($primary_result, $eager_load_model_name."s")):
- $primary_result->{$eager_load_model_name."s"} = array();
+ if(!property_exists($primary_result, $mount_point)):
+ $primary_result->{$mount_point} = array();
endif;
// works for one to many
foreach ($result_cast as $model_eager) :
if($model_eager->{$primary_result_table_name.'_'.$current_pk}===$primary_result->{$current_pk}):
- $primary_result->{$eager_load_model_name."s"}[]=$model_eager;
+ $primary_result->{$mount_point}[]=$model_eager;
endif;
endforeach;
@@ -1778,32 +1876,32 @@ protected function _m2o_join($related_model_name, $where_condition=NULL, $foreig
$related_table_short_name = $this->_related_table_short_name($related_model_name);
$current_table_short_name = $this->_current_table_short_name();
- // foreign key table
- $foreign_key = (isset($foreign_key))?$foreign_key: NULL;
$foreign_key_table = $this->_search_foreignkey($related_model_name, $foreign_key);
// ================================= ONE TO MANY ===================================
-
if($foreign_key_table===$this->_context->table_name()):
+ // foreign key table
+ $foreign_key = (isset($foreign_key))?$foreign_key: $related_table_short_name."_".$related_pk;
// if someone is trying to filter the Many side
// e.g if some is trying to get all products that fall under a certain category
// in a relationship where the category is the one side and the product is the many side
// $this->product_model->filter(array('category_model::id'=>5)));
- $on = $foreign_key_table.".".$related_table_short_name."_".$related_pk."=".$related_table_name.".".$related_pk;
+ $on = $foreign_key_table.".".$foreign_key."=".$related_table_name.".".$related_pk;
$this->_database->join($related_table_name, $on);
else:
-
+ // foreign key is not in the current table
+ // foreign key table
+ $foreign_key = (isset($foreign_key))?$foreign_key: $current_table_short_name."_".$current_pk;
// ========================================== MANY TO ONE =============================
-
// if someone is trying to filter the One side in a one to many relationship
// e.g if some is trying to get the category a product belongs to
// in a relationship where the category is the one side and the product is the many side
// $this->category_model->filter(array('product_model::1d'=>5)));
- $on = $this->_context->table_name().".".$current_pk."=".$related_table_name.".".$current_table_short_name."_".$current_pk;
+ $on = $this->_context->table_name().".".$current_pk."=".$related_table_name.".".$foreign_key;
$this->_database->join($this->_context->{$related_model_name}->table_name(), $on);
endif;
@@ -1837,12 +1935,12 @@ public function _self_reference($related_model_name, $where_condition=NULL, $for
*/
public function _related($model_name){
$current_pk = $this->_current_model_primary_key();
- $related_pk = $this->_related_model_primary_key($model_name);
// check that we have the parent model to get its related data.
if(!isset($this->_context->{$current_pk})):
throw new OrmExceptions(sprintf("Trying to get related data of nothing."));
endif;
+
$related_model_name = NULL;
$foreign_key = NULL;
@@ -1853,6 +1951,8 @@ public function _related($model_name){
$related_model_name = $model_name;
endif;
+ $related_pk = $this->_related_model_primary_key($related_model_name);
+
// related model instance
$this->_load_related_model($related_model_name);
@@ -1885,7 +1985,9 @@ public function _related($model_name){
// foreign key table
$foreign_key_table = $this->_search_foreignkey($related_model_name, $foreign_key);
+
if($foreign_key_table && !$one2one):
+
// try one to many and Many to One
$this->_m2o_join($related_model_name, NULL, $foreign_key);
endif;
@@ -1894,6 +1996,7 @@ public function _related($model_name){
// ************************* Try Many To Many **************************
if($foreign_key_table==FALSE && !$one2one):
+
$this->_m2m_join($related_model_name, NULL, $foreign_key);
endif;
@@ -1949,33 +2052,33 @@ protected function _search_foreignkey($related_model_name, $foreign_key=NULL){
$current_table_id=$this->_current_table_short_name().'_'.$this->_current_model_primary_key();
- // search foreign key of current table on the related table
- if($this->_database->field_exists($current_table_id, $this->_context->{$related_model_name}->table_name())){
- $present = $this->_context->{$related_model_name}->table_name();
- }
-
$related_model_name_id = $related_table_name_short.'_'.$this->_related_model_primary_key($related_model_name);
if($present==FALSE && $foreign_key){
// search foreign key of current table on the related table
- if($this->_database->field_exists($foreign_key, $this->_context->{$related_model_name}->table_name())){
+ if(in_array($foreign_key, $this->_context->{$related_model_name}->fields_names())){
$present = $this->_context->{$related_model_name}->table_name();
}
+
// search foreign key of the related table on current table
- if($this->_database->field_exists($foreign_key, $this->_context->table_name())){
+ if(in_array($foreign_key, $this->_context->fields_names())){
$present = $this->_context->table_name();
}
}
- // ToDo search in related table
// search foreign key of the related table on current model
if($present==FALSE && in_array($related_model_name_id, $this->_context->fields_names())){
$present = $this->_context->table_name();
}
+ // search foreign key of current table on the related table
+ if(in_array($current_table_id, $this->_context->{$related_model_name}->fields_names())){
+ $present = $this->_context->{$related_model_name}->table_name();
+ }
+
return $present;
}
@@ -2019,7 +2122,6 @@ public function _load_related_model($related_model_name){
endif;
}
-
/**
* Does the actual saving
* @internal
@@ -2039,6 +2141,28 @@ public function _save(){
// actual saving
$pk = $this->_current_model_primary_key();
+ // got through the fields trying to find to find field with objects as value
+
+ foreach ($this->_context->fields_names() as $field) {
+
+ // assumes current context is the many side being saved that is it has the foreign key
+ if(is_object($this->_context->{$field}) && $this->_context->{$field} instanceof Queryset):
+ // evaluate the Queryset
+ $field_value = $this->_context->{$field}->value();
+
+ $field_model_name = get_class($field_value);
+ $related_pk = $this->_related_model_primary_key($field_model_name);
+ $this->_context->{$field} = $field_value->{$pk};
+ endif;
+
+ if(is_object($this->_context->{$field}) && !($this->_context->{$field} instanceof Queryset)):
+ $field_model_name = get_class($this->_context->{$field});
+ $related_pk = $this->_related_model_primary_key($field_model_name);
+ $this->_context->{$field} = $this->_context->{$field}->{$pk};
+ endif;
+ }
+
+
if(isset($this->_context->{$pk}) && !empty($this->_context->{$pk})):
$pk_value = $this->_context->{$pk};
$this->_database->where($pk, $this->_context->{$pk});
@@ -2052,8 +2176,6 @@ public function _save(){
return $this->get($pk_value);
}
-
-
/**
* Save Many to Many relations
* @param $model
@@ -2135,6 +2257,18 @@ public function _current_model_primary_key(){
return $primary_key_column;
}
+ /**
+ * @ignore
+ * @param $object
+ * @return mixed
+ *
+ */
+ public function _object_pk_value($object){
+ // this will get the query set
+ $pk = $object->primary_key();
+ return $object->{$pk};
+ }
+
/**
* @ignore
*/