/** * Gets the primary keys of the related records for *-to-many relationships * * @internal * * @param string $class The class to get the related primary keys for * @param array &$values The values for the fActiveRecord class * @param array &$related_records The related records existing for the fActiveRecord class * @param string $related_class The class that is related to the current record * @param string $route The route to follow for the class specified * @return array The primary keys of the related records */ public static function getPrimaryKeys($class, &$values, &$related_records, $related_class, $route = NULL) { fActiveRecord::validateClass($related_class); fActiveRecord::forceConfigure($related_class); $table = fORM::tablize($class); $related_table = fORM::tablize($related_class); $db = fORMDatabase::retrieve($class, 'read'); $schema = fORMSchema::retrieve($class); $route = fORMSchema::getRouteName($schema, $table, $related_table, $route, '*-to-many'); if (!isset($related_records[$related_table])) { $related_records[$related_table] = array(); } if (!isset($related_records[$related_table][$route])) { $related_records[$related_table][$route] = array(); } $related_info =& $related_records[$related_table][$route]; if (!isset($related_info['primary_keys'])) { if (isset($related_info['record_set'])) { $related_info['primary_keys'] = $related_info['record_set']->getPrimaryKeys(); // If we don't have a record set yet we want to use a single SQL query to just get the primary keys } else { $relationship = fORMSchema::getRoute($schema, $table, $related_table, $route, '*-to-many'); $related_pk_columns = $schema->getKeys($related_table, 'primary'); $column_info = $schema->getColumnInfo($related_table); $column = $relationship['column']; $aliased_related_pk_columns = array(); foreach ($related_pk_columns as $related_pk_column) { // We explicitly alias the columns due to SQLite issues $aliased_related_pk_columns[] = $db->escape("%r AS %r", $related_table . '.' . $related_pk_column, $related_pk_column); } if (isset($relationship['join_table'])) { $table_with_route = $table . '{' . $relationship['join_table'] . '}'; } else { $table_with_route = $table . '{' . $relationship['related_column'] . '}'; } $column = $relationship['column']; $related_column = $relationship['related_column']; $params = array($db->escape(sprintf("SELECT %s FROM :from_clause WHERE", join(', ', $aliased_related_pk_columns)) . " %r = ", $table_with_route . '.' . $column)); $params[0] .= $schema->getColumnInfo($table, $column, 'placeholder'); $params[] = $values[$column]; $params[0] .= " :group_by_clause "; if (!($order_bys = self::getOrderBys($class, $related_class, $route))) { $order_bys = array(); foreach ($related_pk_columns as $related_pk_column) { $order_bys[$related_pk_column] = 'ASC'; } } $params[0] .= " ORDER BY "; $params = fORMDatabase::addOrderByClause($db, $schema, $params, $related_table, $order_bys); $params = fORMDatabase::injectFromAndGroupByClauses($db, $schema, $params, $related_table); $result = call_user_func_array($db->translatedQuery, $params); $primary_keys = array(); foreach ($result as $row) { if (sizeof($row) > 1) { $primary_key = array(); foreach ($row as $column => $value) { $value = $db->unescape($column_info[$column]['type'], $value); $primary_key[$column] = $value; } $primary_keys[] = $primary_key; } else { $column = key($row); $primary_keys[] = $db->unescape($column_info[$column]['type'], $row[$column]); } } $related_info['record_set'] = NULL; $related_info['count'] = sizeof($primary_keys); $related_info['associate'] = FALSE; $related_info['primary_keys'] = $primary_keys; } } return $related_info['primary_keys']; }
/** * Builds the related records for all records in this set in one DB query * * @param string $related_class This should be the name of a related class * @param string $route This should be a column name or a join table name and is only required when there are multiple routes to a related table. If there are multiple routes and this is not specified, an fProgrammerException will be thrown. * @return fRecordSet The record set object, to allow for method chaining */ private function prebuild($related_class, $route = NULL) { if (!$this->records) { return $this; } $this->validateSingleClass('prebuild'); // If there are no primary keys we can just exit if (!array_merge($this->getPrimaryKeys())) { return $this; } fActiveRecord::validateClass($related_class); fActiveRecord::forceConfigure($related_class); $db = fORMDatabase::retrieve($this->class, 'read'); $schema = fORMSchema::retrieve($this->class); $related_table = fORM::tablize($related_class); $table = fORM::tablize($this->class); $route = fORMSchema::getRouteName($schema, $table, $related_table, $route, '*-to-many'); $relationship = fORMSchema::getRoute($schema, $table, $related_table, $route, '*-to-many'); $table_with_route = $route ? $table . '{' . $route . '}' : $table; // Build the query out $params = array($db->escape('SELECT %r.*', $related_table)); // If we are going through a join table we need the related primary key for matching if (isset($relationship['join_table'])) { // We explicitly alias the column because of SQLite issues $params[0] .= $db->escape(", %r AS %r", $table_with_route . '.' . $relationship['column'], $relationship['column']); } $params[0] .= ' FROM :from_clause WHERE '; $params = $this->addWhereParams($db, $schema, $params, $route); $params[0] .= ' :group_by_clause ORDER BY '; $params = $this->addOrderByParams($db, $schema, $params, $related_class, $route); $params = fORMDatabase::injectFromAndGroupByClauses($db, $schema, $params, $related_table); // Add the joining column to the group by if (strpos($params[0], 'GROUP BY') !== FALSE) { $params[0] = str_replace(' ORDER BY', $db->escape(', %r ORDER BY', $table . '.' . $relationship['column']), $params[0]); } // Run the query and inject the results into the records $result = call_user_func_array($db->translatedQuery, $params); $total_records = sizeof($this->records); for ($i = 0; $i < $total_records; $i++) { // Get the record we are injecting into $record = $this->records[$i]; $keys = array(); // If we are going through a join table, keep track of the record by the value in the join table if (isset($relationship['join_table'])) { try { $current_row = $result->current(); $keys[$relationship['column']] = $current_row[$relationship['column']]; } catch (fExpectedException $e) { } // If it is a straight join, keep track of the value by the related column value } else { $method = 'get' . fGrammar::camelize($relationship['column'], TRUE); $keys[$relationship['related_column']] = $record->{$method}(); } // Loop through and find each row for the current record $rows = array(); try { while (!array_diff_assoc($keys, $result->current())) { $row = $result->fetchRow(); // If we are going through a join table we need to remove the related primary key that was used for matching if (isset($relationship['join_table'])) { unset($row[$relationship['column']]); } $rows[] = $row; } } catch (fExpectedException $e) { } // Set up the result object for the new record set $set = new fRecordSet($related_class, new ArrayIterator($rows)); // Inject the new record set into the record $method = 'inject' . fGrammar::pluralize($related_class); $record->{$method}($set, $route); } return $this; }