예제 #1
0
 /**
  * Generate the related entities of model/instance
  *
  * @param  object Gas instance
  * @param  mixed  Gas relationship spec
  * @param  array  Resource collection
  * @param  bool   Whether to return the SQL statement or execute then send its result
  * @return object Child Gas 
  */
 public static function generate_entity($gas, $relationship, $resources = array(), $raw = FALSE)
 {
     // Get the relationship properties
     $path = $relationship['path'];
     $child = $relationship['child'];
     $single = $relationship['single'];
     $options = $relationship['options'];
     $roadmap = explode('=', $path);
     // Now we are in serious business
     if (!empty($resources)) {
         // Generate original identifier and entities holder
         $holder = new Data();
         $original_table = $gas->table;
         $original_pk = $gas->primary_key;
         $original_ids = array();
         foreach ($resources as $resource) {
             // Populate the ids
             $original_ids[] = $resource[$original_pk];
             // Generate new token and empty holder for each original identifier
             $token = $original_table . ':' . $original_pk . '.';
             $index = $resource[$original_pk];
             $holder->set("{$token}{$index}", array($index));
         }
     }
     // Generate the tuple
     $tuples = array();
     $index = 0;
     $max = count($roadmap) - 1;
     // The goal is to parse full path :
     //		Model\Foo=>Model\Bar<=Model\Lorem
     //
     // Into paired tuples like :
     //		Model\Foo>Model\Bar
     //		Model\Bar<Model\Lorem
     //
     // `>` or `<`, thus identify entity ownership
     do {
         $dirty_tuple = $roadmap[$index] . $roadmap[$index + 1];
         if (in_array(substr($dirty_tuple, 0, 1), array('>', '<'))) {
             $tuples[] = substr($dirty_tuple, 1);
         } elseif (in_array(substr($dirty_tuple, -1), array('>', '<'))) {
             $tuples[] = substr($dirty_tuple, 0, -1);
         } else {
             $tuples[] = $dirty_tuple;
         }
         $index++;
     } while ($index < $max);
     // Query holder
     $queries = array();
     // Then generate nested query to fetch each record entity
     foreach ($tuples as $level => $tuple) {
         list($domain, $key, $identifier) = self::generate_identifier($tuple);
         if ($level == 0) {
             if (isset($holder)) {
                 if (count($tuples) == 1) {
                     // Easy one, this is one level path
                     $ids = $original_ids = array($resource[$identifier]);
                 } else {
                     // This mean we really have a business
                     $ids = $original_ids;
                 }
             } else {
                 // We handle a single instance here
                 $ids[] = $gas->record->get('data.' . $identifier);
             }
             $queries[] = array($domain, $key, '');
         } else {
             // Get previous tier index
             $lower_level = $queries[$level - 1];
             if (isset($holder)) {
                 // If holder exists we need to also adding corresponding collumn
                 $paired_cols = array_unique(array($identifier, $lower_level[1]));
                 $lower_query = self::generate_clause($lower_level[0], $paired_cols, $lower_level[1], '');
                 $queries[] = array($domain, $key, $lower_query);
             } else {
                 // Straight forward sub-query
                 $lower_query = self::generate_clause($lower_level[0], $identifier, $lower_level[1], $lower_level[2]);
                 $queries[] = array($domain, $key, $lower_query);
             }
         }
     }
     // Parse the ids into string
     $ids = implode(', ', $ids);
     // Finalize entity generator
     if (count($queries) == 1) {
         // We handle one level of relationship, easy...
         $query = array_shift($queries);
         $subquery = $ids;
         $domain = $query[0];
         $candidate = $query[1];
         // If there was a holder, set the identifier
         if (isset($holder)) {
             $holder->set('identifier', $candidate);
         }
     } else {
         // If there was a holder, we have to do something first
         if (isset($holder)) {
             // Before doing anything, get as much info as possible
             $original_queries = $queries;
             // Parse necessary info
             $query = array_pop($queries);
             $subquery = sprintf(array_pop($query), $ids);
             $domain = $query[0];
             $candidate = $query[1];
             // Doing effective sub-queries for `with` marked records
             foreach ($original_queries as $level => $original_query) {
                 if (empty($original_query[2])) {
                     // Take the identifier for further use
                     $holder->set('identifier', $original_query[1]);
                     $holder->set('ids', $original_ids);
                 } else {
                     $sql = sprintf($original_query[2], implode(',', $holder->get('ids')));
                     $subresults = self::query($sql)->result_array();
                     $identifier = $original_query[1];
                     $matched_id = array();
                     $subids = array();
                     foreach ($subresults as $index => $subresult) {
                         $all_identifier = array_keys($subresult);
                         $old_identifier = $holder->get('identifier');
                         if (count($all_identifier) == 1) {
                             $new_identifier = array_shift($all_identifier);
                         } else {
                             $new_identifier = array_diff($all_identifier, array($old_identifier));
                             $new_identifier = array_shift($new_identifier);
                         }
                         $matcher_id = $subresult[$old_identifier];
                         $identifier_id = $subresult[$new_identifier];
                         foreach ($original_ids as $original_id) {
                             if (!is_array($holder->get($token . $original_id))) {
                                 // Do nothing
                             } elseif (is_array($holder->get($token . $original_id))) {
                                 // we have assoc ids
                                 if (in_array($matcher_id, $holder->get($token . $original_id))) {
                                     // Found matched identifier, save it to holder
                                     $matched_id[$original_id][] = $identifier_id;
                                 } else {
                                     // Generate empty values
                                     $matched_id[$original_id][] = NULL;
                                 }
                             } else {
                                 // We've lost!
                                 throw new \InvalidArgumentException('empty_arguments:' . __METHOD__);
                             }
                         }
                         // Save the identifier ids for further use
                         $subids[] = $identifier_id;
                     }
                     // Make sure we have unique ids
                     $subids = array_unique($subids);
                     sort($subids);
                     // Save above process into holder Data
                     $holder->set('ids', $subids);
                     $holder->set('identifier', $identifier);
                     // Perform checking to assign each new identifier id
                     // For further process, into each original ids
                     foreach ($matched_id as $id => $matched) {
                         $holder->set($token . $id, array_filter($matched));
                     }
                 }
             }
             // Build the subquery
             $subquery = implode(', ', $holder->get('ids'));
         } else {
             // We have more than one tiers level, get the last...
             $query = array_pop($queries);
             $subquery = sprintf(array_pop($query), $ids);
             $domain = $query[0];
             $candidate = $query[1];
         }
     }
     // Initiate empty additional queries
     $order_by = '';
     $limit = '';
     // Initial select would be SELECT *
     // unless there are pre-query option to overide it
     $key = '*';
     // Do we have pre-process query options ?
     if (count($options) > 0) {
         $additional_queries = self::generate_options($options);
         // Do we need to overide the default key for SELECT clause ?
         if (array_key_exists('select', $additional_queries)) {
             $key = $additional_queries['select'];
             // Lets make sure the identifier was included
             if (!in_array($candidate, $key)) {
                 $key[] = $candidate;
             }
         }
         // Do we have ORDER BY clause ?
         if (array_key_exists('order_by', $additional_queries)) {
             $order_by = " ORDER BY `{$domain}`." . $additional_queries['order_by'];
         }
         // Do we have LIMIT clause ?
         if (array_key_exists('limit', $additional_queries)) {
             $limit = ' LIMIT ' . $additional_queries['limit'];
         }
     }
     // Finalize the SQL statement
     $sql = self::generate_clause($domain, $key, $candidate, $subquery);
     $sql = strpos($sql, '%s') !== FALSE ? sprintf($sql, $ids) : $sql;
     $sql .= !empty($order_by) ? $order_by : '';
     $sql .= !empty($limit) ? $limit : '';
     // Do we need to continue, or just return the full SQL statement ?
     if ($raw) {
         return $sql;
     }
     // By now, we could generate the result
     $childs = array();
     $res = self::query($sql)->result_array();
     // In case we handle a holder...
     $matched_id = array();
     foreach ($res as $row) {
         // Hydrate child entities
         $child_instance = new $child($row);
         $child_instance->empty = FALSE;
         // We have associative ids to check
         if (isset($holder)) {
             foreach ($original_ids as $original_id) {
                 // Get the identifier to check
                 $matcher_id = $row[$holder->get('identifier')];
                 if (count($holder->get($token)) == 1) {
                     // One level path...
                     $matched_id[$resource[$original_pk]][] = $child_instance;
                 } elseif (in_array($matcher_id, $holder->get($token . $original_id))) {
                     // We have assoc ids to check against it
                     $matched_id[$original_id][] = $child_instance;
                 } else {
                     // Not found
                     $matched_id[$original_id][] = NULL;
                 }
             }
         }
         $childs[] = $child_instance;
     }
     // All done
     if (isset($holder)) {
         $final_key = substr($token, 0, -1);
         list($table, $identifier) = explode(':', $final_key);
         // Build the holder
         $holder->set('data', array_filter($matched_id));
         $holder->set('identifier', $identifier);
         $holder->set('ids', array_keys($matched_id));
         // Transfer into save place, then unset the holder
         $final_entities = $holder;
         unset($holder);
         return $final_entities;
     } else {
         return $single ? array_shift($childs) : $childs;
     }
 }
예제 #2
0
 /**
  * Execute the compilation command
  *
  * @param  object Gas instance
  * @return object Finished Gas 
  */
 protected static function _execute($gas)
 {
     // Build the tasks tree
     $tasks = self::_play_record($gas->recorder);
     // Mark every compile process into our caching pool
     self::cache_start($tasks);
     // Prepare tasks bundle
     $engine = get_class(static::$db);
     $compiler = array('gas' => $gas);
     $executor = static::$dictionary['executor'];
     $write = array_slice($executor, 0, 6);
     $flag = array('condition', 'selector');
     $bundle = array('engine' => $engine, 'compiler' => $compiler, 'write' => $write, 'flag' => $flag);
     // Assign the task to the right person
     self::$task_manager = $bundle;
     // Lets dance...
     array_walk($tasks, function ($task_list, $key) use(&$tasks) {
         // Only sort if there are valid task and the task manager hold its task list
         if (!empty($task_list) or !empty(\Gas\Core::$task_manager)) {
             array_walk($task_list, function ($arguments, $key, $task) use(&$task_list) {
                 // Only do each task if the task manager hold its task list
                 if (!empty(\Gas\Core::$task_manager)) {
                     // Diagnose the task
                     $action = key($arguments);
                     $args = array_shift($arguments);
                     $flag = in_array($task, \Gas\Core::$task_manager['flag']);
                     $write = in_array($action, \Gas\Core::$task_manager['write']);
                     $gas = \Gas\Core::$task_manager['compiler']['gas'];
                     $table = $gas->table;
                     if (!$flag) {
                         // Find within cache resource collection
                         if ($action == 'get' && \Gas\Core::validate_cache() && !\Gas\Core::changed_resource($table)) {
                             $res = \Gas\Core::fetch_cache();
                             \Gas\Core::reset_query();
                         } else {
                             $dbal_method = array(\Gas\Core::$db, $action);
                             $res = call_user_func_array($dbal_method, $args);
                             \Gas\Core::cache_end($res);
                         }
                         // Post-processing query
                         if ($write) {
                             // Track the resource for any write operations
                             \Gas\Core::track_resource($table, $action);
                         } elseif ($action == 'get') {
                             // Hydrate the gas instance
                             $instances = array();
                             $entities = array();
                             $ids = array();
                             $model = $gas->model();
                             $extension = $gas->extension;
                             $includes = $gas->related->get('include', array());
                             $relation = $gas->meta->get('entities');
                             // Do we have entities to eagerly-loaded?
                             if (count($includes)) {
                                 // Then generate new colleciton holder for it
                                 $tuples = new \Gas\Data();
                             }
                             // Get the array of fetched rows
                             $results = $res->result_array();
                             // Generate the entitiy records
                             foreach ($results as $result) {
                                 // Passed the result as record
                                 $instance = new $model($result);
                                 $instance->empty = FALSE;
                                 foreach ($includes as $include) {
                                     if (array_key_exists($include, $relation)) {
                                         $table = $instance->table;
                                         $pk = $instance->primary_key;
                                         $identifier = $instance->record->get('data.' . $pk);
                                         $concenate = $table . ':' . $pk . ':' . $identifier;
                                         $tuple = $relation[$include];
                                         $type = $tuple['type'];
                                         if ($tuples->get('entities.' . $include)) {
                                             // Retrieve this entity
                                             $assoc_entities = $tuples->get('entities.' . $include);
                                         } else {
                                             $assoc_entities = \Gas\Core::generate_entity($gas, $tuple, $results);
                                             $tuples->set('entities.' . $include, $assoc_entities);
                                         }
                                         // Assign the included entity, respectively
                                         $entity = array_values(array_filter($assoc_entities->get('data.' . $identifier, array())));
                                         $related_entity = $type == 'has_many' ? $entity : current($entity);
                                         $instance->related->set('entities.' . $include, $related_entity);
                                     }
                                 }
                                 // Pool to instance holder and unset the instance
                                 $instances[] = $instance;
                                 unset($instance);
                             }
                             // Determine whether to return an instance or a collection of instance(s)
                             $res = count($instances) > 1 ? $instances : array_shift($instances);
                             // Do we need to return the result, or passed into some extension?
                             if (!empty($extension) && $extension instanceof Extension) {
                                 $res = $extension->__init($res);
                             }
                         }
                         // Tell task manager to take a break, and fill the resource holder
                         \Gas\Core::$task_manager = array();
                         \Gas\Core::$thread_resource = $res;
                     } else {
                         // Return the native DB driver method execution
                         return call_user_func_array(array(\Gas\Core::$db, $action), $args);
                     }
                 }
             }, $key);
         }
     });
     // Get the result and immediately flush the temporary resource holder
     $resource = self::$thread_resource and self::$thread_resource = NULL;
     // The compilation is done, send the song to listen
     return $resource;
 }