/** * 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; } }
/** * 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; }