'Medtr4g030710' * ); * $feature = chado_generate_var('feature', $values); * @endcode * * The $values array passed to this function can be of the same format used * by the chado_select_record() function. * * If a field is a foreign key then its value is an object that contains * key/value pairs for that record. The following code provides examples * for retrieving values associated with the record, either as columns in the * original Chado table or as columns in linked records through foreign keys: * @code * // Get the feature name. * $name = $feature->name; * // Get the feature unique name. * $uniquename = $feature->uniquename; * // Get the feature type. Because the type name is obtained via * // a foreign key with the cvterm table, the objects are nested * // and we can follow the foreign key fields to retrieve those values * $type = $feature->type_id->name; * // Get the name of the vocabulary. * $cv = $feature->type_id->cv_id->name; * // Get the vocabulary id. * $cv_id = $feature->type_id->cv_id->cv_id; * @endcode * * * This will return an object if there is only one feature with the name * Medtr4g030710 or it will return an array of feature objects if more than one * feature has that name. * * Note to Module Designers: Fields can be excluded by default from these * objects by implementing one of the following hooks: * - hook_exclude_field_from_tablename_by_default (where tablename is the * name of the table): This hook allows you to add fields to be excluded * on a per table basis. Simply implement this hook to return an array of * fields to be excluded. The following example will ensure that * feature.residues is excluded from a feature object by default: * @code * mymodule_exclude_field_from_feature_by_default() { * return array('residues' => TRUE); * } * @endcode * - hook_exclude_type_by_default: * This hook allows you to exclude fields using conditional. This * function should return an array of postgresql types mapped to criteria. * If the field types of any table match the criteria then the field * is excluded. Tokens available in criteria are >field_value< * and >field_name<. The following example will exclude all text * fields with a length > 50. Thus if $feature.residues is longer than * 50 it will be excluded, otherwise it will be added. * @code * mymodule_exclude_type_by_default() { * return array('text' => 'length(>field_value< ) > 50'); * } * @endcode * * * @param $table * The name of the base table to generate a variable for * @param $values * A select values array that selects the records you want from the base table * (this has the same form as chado_select_record) * @param $base_options * An array containing options for the base table. For example, an * option of 'order_by' may be used to sort results in the base table * if more than one are returned. The options must be compatible with * the options accepted by the chado_select_record() function. * Additionally, These options are available for this function: * -return_array: * can be provided to force the function to always return an array. Default * behavior is to return a single record if only one record exists or to * return an array if multiple records exist. * - include_fk: * an array of FK relationships to follow. By default, the * chado_select_record function will follow all FK relationships but this * may generate more queries then is desired slowing down this function * call when there are lots of FK relationships to follow. Provide an * array specifying the fields to include. For example, if expanding a * property table (e.g. featureprop) and you want the CV and accession * but do not want the DB the following array would work: * * $table_options = [ * 'include_fk' => [ * 'type_id' => [ * 'cv_id' => 1, * 'dbxref_id' => 1, * ] * ] * ); * * The above array will expand the 'type_id' of the property table but only * further expand the cv_id and the dbxref_id and will go no further. * - pager: * Use this option if it is desired to return only a subset of results * so that they may be shown within a Drupal-style pager. This should be * an array with two keys: 'limit' and 'element'. The value of 'limit' * should specify the number of records to return and 'element' is a * unique integer to differentiate between pagers when more than one * appear on a page. The 'element' should start with zero and increment by * one for each pager. * * @return * Either an object (if only one record was selected from the base table) * or an array of objects (if more than one record was selected from the * base table). If the option 'return_array' is provided the function * always returns an array. * * @ingroup tripal_chado_variables_api */ function chado_generate_var($table, $values, $base_options = []) { $all = new stdClass(); $return_array = 0; if (array_key_exists('return_array', $base_options)) { $return_array = 1; } $include_fk = FALSE; if (array_key_exists('include_fk', $base_options)) { $include_fk = $base_options['include_fk']; } $pager = []; if (array_key_exists('pager', $base_options)) { $pager = $base_options['pager']; } // get description for the current table------------------------------------- $table_desc = chado_get_schema($table); if (!$table_desc or count($table_desc) == 0) { tripal_report_error('tripal_chado', TRIPAL_ERROR, "chado_generate_var: The table '%table' has not been defined " . "and cannot be expanded. If this is a custom table, please add it using the Tripal " . "custom table interface. Values: %values", ['%table' => $table, '%values' => print_r($values, TRUE)]); if ($return_array) { return []; } return FALSE; } $table_primary_key = $table_desc['primary key'][0]; $table_columns = array_keys($table_desc['fields']); // Expandable fields without value needed for criteria----------------------- // Add in the default expandable arrays // These are used for later expanding fields, tables, foreign keys and nodes $all->expandable_fields = []; $all->expandable_foreign_keys = []; if (array_key_exists('referring_tables', $table_desc) and $table_desc['referring_tables']) { $all->expandable_tables = $table_desc['referring_tables']; } else { $all->expandable_tables = []; } $all->expandable_nodes = []; // Get fields to be removed by name................................. // This gets all implementations of hook_exclude_field_from__by_default() // where
is the current table a variable is being created for. // This allows modules to specify that some fields should be excluded by default // For example, tripal core provides a tripal_chado_exclude_field_from_feature_by_default() // which says that we usually don't want to include the residues field by // default since it can be very large and cause performance issues. // If a field is excluded by default it can always be expanded at a later // point by calling chado_expand_var($chado_var, 'field', // ); // First get an array of all the fields to be removed for the current table // module_invoke_all() is drupal's way of invoking all implementations of the // specified hook and merging all of the results. // $fields_to_remove should be an array with the keys matching field names // and the values being strings to be executed using php_eval() to determine // whether to exclude the field (evaluates to TRUE) or not (evaluates to FALSE) $fields_to_remove = module_invoke_all('exclude_field_from_' . $table . '_by_default'); // Now, for each field to be removed foreach ($fields_to_remove as $field_name => $criteria) { //Replace with the current field name $field_name_safe = preg_replace("/\'\"\\\/", '\\1', $field_name); $criteria = preg_replace('/ /', $field_name_safe, $criteria); // If field_value needed we can't deal with this field yet. if (preg_match('/ /', $criteria)) { break; } // If criteria then remove from query // @coder-ignore: only module designers can populate $criteria -not a // security risk. $success = php_eval(''); if ($success) { unset($table_columns[array_search($field_name, $table_columns)]); unset($fields_to_remove[$field_name]); $all->expandable_fields[] = $table . '.' . $field_name; } } // Get fields to be removed by type................................ // This gets all implementations of hook_exclude_type_by_default(). // This allows modules to specify that some types of fields should be excluded // by default For example, tripal core provides a // tripal_chado_exclude_type_by_default() which says that text fields are // often very large and if they are longer than 250 characters then // we want to exclude them by default // If a field is excluded by default it can always be expanded at a later // point by calling chado_expand_var($chado_var, 'field', //); // First get an array of all the types of fields to be removed for the current // table module_invoke_all() is drupal's way of invoking all implementations // of the specified hook and merging all of the results. // $types_to_remove should be an array with the keys matching field names // and the values being strings to be executed using php_eval() to determine // whether to exclude the field (evaluates to TRUE) or not (evaluates to FALSE) // (ie: array('text' => 'strlen(" ") > 100'); $types_to_remove = module_invoke_all('exclude_type_by_default'); // Get a list of all the types of fields // the key is the type of field and the value is an array of fields of this // type. $field_types = []; foreach ($table_desc['fields'] as $field_name => $field_array) { $field_types[$field_array['type']][] = $field_name; } // We want to use the types to remove in conjunction with our table field // descriptions to determine which fields might need to be removed. foreach ($types_to_remove as $field_type => $criteria) { // If there are fields of that type to remove. if (isset($field_types[$field_type])) { // Do any processing needed on the php criteria //replace with the current field name. $field_name_safe = preg_replace('/\'|"|\\\/', '\\1', $field_name); $criteria = preg_replace('/ /', $field_name_safe, $criteria); foreach ($field_types[$field_type] as $field_name) { // if field_value needed we can't deal with this field yet if (preg_match('//', $criteria)) { $fields_to_remove[$field_name] = $criteria; continue; } // If criteria then remove from query // (as long as is not needed for the criteria to be // evaluated) @coder-ignore: only module designers can populate //$criteria -not a security risk. $success = php_eval(''); if ($success) { unset($table_columns[array_search($field_name, $table_columns)]); $all->expandable_fields[] = $table . '.' . $field_name; } } // End of foreach field of that type. } } // End of foreach type to be removed. // Get the values for the record in the current table------------------------- $results = chado_select_record($table, $table_columns, $values, $base_options); if ($results) { // Iterate through each result. foreach ($results as $key => $object) { // Add empty expandable_x arrays. $object->expandable_fields = $all->expandable_fields; $object->expandable_foreign_keys = $all->expandable_foreign_keys; $object->expandable_tables = $all->expandable_tables; $object->expandable_nodes = $all->expandable_nodes; // add curent table $object->tablename = $table; // For Tripal v2 compatibility // check if the current table maps to a node type------------------------- // If this table is connected to a node there will be a chado_tablename // table in drupal. $base_tables = chado_get_base_tables(); if (module_exists('tripal_core') and db_table_exists('chado_' . $table)) { // That has a foreign key to this one ($table_desc['primary key'][0] // and to the node table (nid). $sql = " SELECT $table_primary_key, nid FROM {chado_$table} WHERE $table_primary_key = :$table_primary_key "; $mapping = db_query($sql, [":$table_primary_key" => $object->{$table_primary_key}])->fetchObject(); if ($mapping and $mapping->$table_primary_key) { $object->nid = $mapping->nid; $object->expandable_nodes[] = $table; } } // Check to see if the current record maps to an entity. Because // multiple bundles can map to the same table we have to check // all bundles for this table. $entity_id = chado_get_record_entity_by_table($table, $object->{$table_primary_key}); if ($entity_id) { $object->entity_id = $entity_id; } // Remove any fields where criteria needs to be evalulated---------------- // The fields to be removed can be populated by implementing either // hook_exclude_field_from_
_by_default() where
is the // current table OR hook_exclude_type_by_default() where there are fields // of the specified type in the current table It only reaches this point // if the criteria specified for whether or not to exclude the field // includes which means it has to be evaluated after // the query has been executed. foreach ($fields_to_remove as $field_name => $criteria) { // If the field is an object then we don't support exclusion of it // For example, if the field is a foreign key if (!isset($object->{$field_name})) { break; } // Replace with the actual value of the field from the // query. $field_name_safe = preg_replace('/\'|"|\\\/', '\\1', $object->{$field_name}); $criteria = preg_replace('//', $field_name_safe, $criteria); // evaluate the criteria, if TRUE is returned then exclude the field // excluded fields can be expanded later by calling // chado_expand_var($var, 'field', ); $success = php_eval(''); if ($success) { unset($object->{$field_name}); $object->expandable_fields[] = $table . '.' . $field_name; } } // Recursively follow foreign key relationships nesting objects as we go------------------------ if (array_key_exists('foreign keys', $table_desc) and $table_desc['foreign keys']) { foreach ($table_desc['foreign keys'] as $foreign_key_array) { $foreign_table = $foreign_key_array['table']; foreach ($foreign_key_array['columns'] as $foreign_key => $primary_key) { // Note: Foreign key is the field in the current table whereas // primary_key is the field in the table referenced by the foreign // key, don't do anything if the foreign key is empty if (empty($object->{$foreign_key})) { continue; } if (is_array($include_fk)) { // Don't recurse if the callee has supplied an $fk_include list // and this FK table is not in the list. if (is_array($include_fk) and !array_key_exists($foreign_key, $include_fk)) { $object->expandable_foreign_keys[] = $table . '.' . $foreign_key . ' => ' . $foreign_table; continue; } } // If we have the option but it is not an array then we don't // recurse any further. if ($include_fk === TRUE) { $object->expandable_foreign_keys[] = $table . '.' . $foreign_key . ' => ' . $foreign_table; continue; } // Get the record from the foreign table. $foreign_values = [$primary_key => $object->{$foreign_key}]; $options = []; if (is_array($include_fk)) { $options['include_fk'] = $include_fk[$foreign_key]; } $foreign_object = chado_generate_var($foreign_table, $foreign_values, $options); // Add the foreign record to the current object in a nested manner. $object->{$foreign_key} = $foreign_object; // Flatten expandable_x arrays so only in the bottom object. if (property_exists($object->{$foreign_key}, 'expandable_fields') and is_array($object->{$foreign_key}->expandable_fields)) { $object->expandable_fields = array_merge( $object->expandable_fields, $object->{$foreign_key}->expandable_fields ); unset($object->{$foreign_key}->expandable_fields); } if (property_exists($object->{$foreign_key}, 'expandable_foreign_keys') and is_array($object->{$foreign_key}->expandable_foreign_keys)) { $object->expandable_foreign_keys = array_merge( $object->expandable_foreign_keys, $object->{$foreign_key}->expandable_foreign_keys ); unset($object->{$foreign_key}->expandable_foreign_keys); } if (property_exists($object->{$foreign_key}, 'expandable_tables') and is_array($object->{$foreign_key}->expandable_tables)) { $object->expandable_tables = array_merge( $object->expandable_tables, $object->{$foreign_key}->expandable_tables ); unset($object->{$foreign_key}->expandable_tables); } if (property_exists($object->{$foreign_key}, 'expandable_nodes') and is_array($object->{$foreign_key}->expandable_nodes)) { $object->expandable_nodes = array_merge( $object->expandable_nodes, $object->{$foreign_key}->expandable_nodes ); unset($object->{$foreign_key}->expandable_nodes); } } } $results[$key] = $object; } } } // Convert the results into an array. $results_arr = []; foreach ($results as $record) { $results_arr[] = $record; } // Check only one result returned. if (!$return_array) { if (sizeof($results_arr) == 1) { // Add results to object. return $results_arr[0]; } elseif (!empty($results_arr)) { return $results_arr; } else { // No results returned. } } // The caller has requested results are always returned as // an array. else { if (!$results_arr) { return []; } else { return $results_arr; } } } /** * Retrieves fields, or tables that were excluded by default from a variable. * * The chado_generate_var() function automatically excludes some * fields and tables from the default form of a variable. Fields that are * extremely long, such as text fields are automatically excluded to prevent * long page loads. Linking tables that have a many-to-one relationship with * the record are also excluded. This function allows for custom expansion * of the record created by chado_generate_var() by specifyin the field and * tables that should be added. * * Example Usage: * * @code * // Get a chado object to be expanded * $values = array( * 'name' => 'Medtr4g030710' * ); * $features = chado_generate_var('feature', $values); * // Expand the feature.residues field * $feature = chado_expand_var($feature, 'field', 'feature.residues'); * // Expand the feature properties (featureprop table) * $feature = chado_expand_var($feature, 'table', 'featureprop'); * @endcode * * If a field is requested, it's value is added where it normally is expected * in the record. If a table is requested then a new key/value element is * added to the record. The key is the table's name and the value is an * array of records (of the same type created by chado_generate_var()). For * example, expanding a 'feature' record to include a 'pub' record via the * 'feature_pub' table. The following provides a simple example for how * the 'feature_pub' table is added. * * @code * array( * 'feature_id' => 1 * 'name' => 'blah', * 'uniquename' => 'blah', * .... * 'feature_pub => array( * [pub object], * [pub object], * [pub object], * [pub object], * ) * ) * @endcode * * where [pub object] is a record of a publication as created by * chado_generate_var(). * * If the requested table has multiple foreign keys, such as the 'featureloc' * or 'feature_genotype' tables, then an additional level is added to the * array where the foreign key column names are added. An example feature * record with an expanded featureloc table is shown below: * * @code * array( * 'feature_id' => 1 * 'name' => 'blah', * 'uniquename' => 'blah', * .... * 'featureloc => array( * 'srcfeature_id' => array( * [feature object], * ... * ) * 'feature_id' => array( * [feature object], * ... * ) * ) * ) * @endcode * * @param $object * This must be an object generated using chado_generate_var() * @param $type * Indicates what is being expanded. Must be one of 'field', 'foreign_key', * 'table', 'node'. While field and node are self-explanitory, it might help * to note that 'table' refers to tables that have a foreign key pointing to * the current table (ie: featureprop is a table that can be expanded for * features) and 'foreign_key' expands a foreign key in the current table * that might have been excluded (ie: feature.type_id for features). * @param $to_expand * The name of the field/foreign_key/table/node to be expanded * @param $table_options * - order_by: * An array containing options for the base table. For example, an * option of 'order_by' may be used to sort results in the base table * if more than one are returned. The options must be compatible with * the options accepted by the chado_select_record() function. * - return_array: * Additionally, The option 'return_array' can be provided to force * the function to expand tables as an array. Default behavior is to expand * a table as single record if only one record exists or to expand as an * array if multiple records exist. * - include_fk: * an array of FK relationships to follow. By default, the * chado_expand_var function will follow all FK relationships but this * may generate more queries then is desired slowing down this function call * when there are lots of FK relationships to follow. Provide an array * specifying the fields to include. For example, if expanding a property * table (e.g. featureprop) and you want the CV and accession but do not * want the DB the following array would work: * $table_options = array( * 'include_fk' => array( * 'type_id' => array( * 'cv_id' => 1, * 'dbxref_id' => 1, * ) * ) * ); * * The above array will expand the 'type_id' of the property table but only * further expand the cv_id and the dbxref_id and will go no further. * - pager: * Use this option if it is desired to return only a subset of results * so that they may be shown within a Drupal-style pager. This should be * an array with two keys: 'limit' and 'element'. The value of 'limit' * should specify the number of records to return and 'element' is a * unique integer to differentiate between pagers when more than one * appear on a page. The 'element' should start with zero and increment by * one for each pager. This only works when type is a 'table'. * - filter: * This options is only used where type=table and allows you to * expand only a subset of results based on the given criteria. Criteria * should provided as an array of [field name] => [value] similar to the * values array provided to chado_generate_var(). For example, when * expanding the featureprop table for a feature, you will already get only * properties for that feature, this option allows you to further get only * properties of a given type by passing in * array('type_id' => array('name' => [name of type])) * * @return * A chado object supplemented with the field/table/node requested to be * expanded. If the type is a table and it has already been expanded no * changes is made to the returned object * * * @ingroup tripal_chado_variables_api */ function chado_expand_var($object, $type, $to_expand, $table_options = []) { // Make sure we have a value. if (!$object) { $trace = (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); tripal_report_error('tripal_chado', TRIPAL_ERROR, 'Missing $object argument to the chado_expand_var function from %caller (line: %line)', ['%caller' => $trace[1]['function'], '%line' => $trace[0]['line']]); return $object; } // Check to see if we are expanding an array of objects. if (is_array($object)) { foreach ($object as $index => $o) { $object[$index] = chado_expand_var($o, $type, $to_expand); } return $object; } // Get the base table name. $base_table = $object->tablename; switch ($type) { case "field": //------------------------------------------------------------ if (preg_match('/(\w+)\.(\w+)/', $to_expand, $matches)) { $tablename = $matches[1]; $fieldname = $matches[2]; $table_desc = chado_get_schema($tablename); // BASE CASE: the field is from the current table. if ($base_table == $tablename) { // Use the table description to fully describe the current object // in a $values array to be used to select the field from chado. $values = []; foreach ($table_desc['primary key'] as $key) { if (property_exists($object, $key)) { $values[$key] = $object->{$key}; } } // Retrieve the field from Chado. $results = chado_select_record($tablename, [$fieldname], $values); // Check that the field was retrieved correctly. if (isset($results[0])) { $object->{$fieldname} = $results[0]->{$fieldname}; $object->expanded = $to_expand; } // If it wasn't retrieved correctly, we need to warn the administrator. } // RECURSIVE CASE: the field is in a nested object. else { // We want to look at each field and if it's an object then we want to // attempt to expand the field in it via recursion. foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { $object->{$field_name} = chado_expand_var( $field_value, 'field', $to_expand ); } } // End of for each field in the current object. } } // Otherwise we weren't able to extract the parts of the field to expand // Thus we will warn the administrator. else { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: Field (%field) not in the right format. " . "It should be .', ['%field' => $to_expand]); } break; case "foreign_key": //----------------------------------------------------- if (preg_match('/(\w+)\.(\w+) => (\w+)/', $to_expand, $matches)) { $table_name = $matches[1]; $field_name = $matches[2]; $foreign_table = $matches[3]; $table_desc = chado_get_schema($table_name); // BASE CASE: The foreign key is from the current table. if ($base_table == $table_name) { // Get the value of the foreign key from the object $field_value = $object->{$field_name}; // Get the name of the field in the foreign table using the table // description For example, with the // feature.type_id => cvterm.cvterm_id we need cvterm_id $foreign_field_name = FALSE; foreach ($table_desc['foreign keys'][$foreign_table]['columns'] as $left => $right) { if ($right == $field_name) { $foreign_field_name = $left; } } // Check that we were able to determine the field name in the foreign // table. if ($foreign_field_name) { // Generate a chado variable of the foreign key // For example, if the foreign key to expand is feature.type_id // then we want to generate a chado cvterm variable that matches the // feature.type_id. $foreign_var = chado_generate_var( $foreign_table, // thus in the example above, generate a cvterm var [$foreign_field_name => $field_value], // where the cvterm.cvterm_id = feature.type_id value $table_options //pass in the same options given to this function ); // Check that the foreign object was returned. if ($foreign_var) { // It was so now we can add this chado variable to our current // object in place of the key value. $object->{$field_name} = $foreign_var; $object->expanded = $to_expand; } // Otherwise we weren't able to expand the foreign key else { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: unable to retrieve the object desribed by the foreign key while trying to expand %fk.', ['%fk' => $to_expand]); } } // Else we were unable to determine the field name in the foreign table. else { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: unable to determine the field name in the table the foreign key points to while trying to expand %fk.', ['%fk' => $to_expand]); } } // RECURSIVE CASE: Check any nested objects. else { foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { $object->{$field_name} = chado_expand_var( $field_value, 'foreign_key', $to_expand ); } } //End of for each field in the current object. } } // Otherwise we weren't able to extract the parts of the foreign key to // expand thus we will warn the administrator. else { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: foreign_key (%fk) not in the right format. " . "It should be .', ['%fk' => $to_expand]); } break; case "table": //------------------------------------------------------------ $foreign_table = $to_expand; // BASE CASE: don't expand the table it already is expanded if (property_exists($object, $foreign_table)) { return $object; } $foreign_table_desc = chado_get_schema($foreign_table); // If we don't get a foreign_table (which could happen of a custom // table is not correctly defined or the table name is mispelled then we // should return gracefully. if (!is_array($foreign_table_desc)) { return $object; } // BASE CASE: If it's connected to the base table via a FK constraint // then we have all the information needed to expand it now. if (array_key_exists($base_table, $foreign_table_desc['foreign keys'])) { foreach ($foreign_table_desc['foreign keys'][$base_table]['columns'] as $left => $right) { // if the FK value in the base table is not there then we can't expand // it, so just skip it. if (!$object->{$right}) { continue; } // If the user wants to limit the results they expand, make sure // those criteria are taken into account. if (isset($table_options['filter'])) { if (is_array($table_options['filter'])) { $filter_criteria = $table_options['filter']; $filter_criteria[$left] = $object->{$right}; } else { // If they supplied criteria but it's not in the correct format // then warn them but proceed as though criteria was not supplied. $filter_criteria = [$left => $object->{$right}]; tripal_report_error('tripal_chado', TRIPAL_WARNING, 'chado_expand_var: unable to apply supplied filter criteria since it should be an array. You supplied %criteria', ['%criteria' => print_r($table_options['filter'], TRUE)] ); } } else { $filter_criteria = [$left => $object->{$right}]; } // Generate a new object for this table using the FK values in the // base table. $new_options = $table_options; $foreign_object = chado_generate_var($foreign_table, $filter_criteria, $new_options); // If the generation of the object was successful, update the base // object to include it. if ($foreign_object) { // In the case where the foreign key relationship exists more // than once with the same table we want to alter the array // structure to include the field name. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table}->{$left} = $foreign_object; $object->expanded = $to_expand; } else { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table} = $foreign_object; $object->expanded = $to_expand; } } // If the object returned is NULL then handle that. else { // In the case where the foreign key relationship exists more // than once with the same table we want to alter the array // structure to include the field name. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table}->{$left} = NULL; } else { $object->{$foreign_table} = NULL; } } } } // RECURSIVE CASE: if the table is not connected directly to the current // base table through a foreign key relationship, then maybe it has a // relationship to one of the nested objects. else { // We need to recurse -the table has a relationship to one of the nested // objects. We assume it's a nested object if the value of the field is // an object. $did_expansion = 0; foreach ((array) $object as $field_name => $field_value) { // CASE #1: This field is an already expanded foreign key and the // table to be expanded is in the table referenced by the foreign key. // First of all it can only be expanded if it's an object // And if it's a foreign key it should have a tablename property. if (is_object($field_value) AND property_exists($field_value, 'tablename')) { $object->{$field_name} = chado_expand_var($field_value, 'table', $foreign_table); } // CASE #2: This field is an already expanded object (ie: the field is // actually the expanded table name) and the table to be expanded is // related to it. // Check to see if the $field_name is a valid chado table, we don't // need to call chado_expand_var on fields that aren't tables. $check = chado_get_schema($field_name); if ($check) { $did_expansion = 1; $object->{$field_name} = chado_expand_var($field_value, 'table', $foreign_table); } } // If we did not expand this table we should return a message that the // foreign tabl could not be expanded. if (!$did_expansion) { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: Could not expand %table. ' . 'The table is either not related to the base object through a foreign key relationships or ' . 'it is already expanded. First check the object to ensure it doesn’t already contain the ' . 'data needed and otherwise check the table definition using chado_get_schema() to ensure ' . 'a proper foreign key relationship is present.', ['%table' => $foreign_table]); } } break; case "node": //------------------------------------------------------------- // BASE CASE: if the node to be expanded is for our base table, then just // expand it. if ($object->tablename == $to_expand) { // Load the node based on the current objects nid (node primary key). $node = NULL; if (property_exists($object, 'nid')) { $node = node_load($object->nid); } // Try to get the nid based on the tablename. else { // Invoke all hook_node_info to avoid hard-coding the chado_$table // assumption.. foreach (module_invoke_all('node_info') as $node_info) { if (array_key_exists('chado_node_api', $node_info)) { if ($node_info['chado_node_api']['base_table'] == $object->tablename) { $key_name = $node_info['chado_node_api']['base_table'] . '_id'; $nid = chado_get_nid_from_id( $node_info['chado_node_api']['base_table'], $object->{$key_name}, $node_info['base']); if ($nid > 0) { $object->nid = $nid; $node = node_load($nid); break; } } } } } // If we have successfully loaded the node... if ($node) { // Move expandable arrays from the object into the node. $object->expanded = $to_expand; $node->expandable_fields = $object->expandable_fields; unset($object->expandable_fields); $node->expandable_tables = $object->expandable_tables; unset($object->expandable_tables); $node->expandable_nodes = $object->expandable_nodes; unset($object->expandable_nodes); // The node becomes the base object with the obejct added to it. // For example, we may start with a feature object with a name, // uniquename , type, etc. After expanding we will return the node and // at $node->feature you will find the original object. $node->{$base_table} = $object; $object = $node; } // Else we were unable to load the node. else { // Warn the administrator if (isset($object->nid)) { tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: No node matches the nid (%nid) supplied.', ['%nid' => $object->nid]); } else { tripal_report_error('tripal_chado', TRIPAL_NOTICE, 'chado_expand_var: There is no node for the current object:
%object
', ['%object' => print_r($object, TRUE)]); } } //End of if node. } // RECURSIVE CASE: check to see if the node to be expanded associates with // a chado table within one of the nested objects. else { // We need to recurse -the node to expand is one of the nested objects // We assume it's a nested object if the field value is an object. foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { $object->{$field_name} = chado_expand_var( $field_value, 'node', $to_expand ); } } // End of for each field in the current object. } break; // The $type to be expanded is not yet supported. default: tripal_report_error('tripal_chado', TRIPAL_ERROR, 'chado_expand_var: Unrecognized type (%type). Should be one of "field", "table", "node".', ['%type' => $type]); return FALSE; } // Move expandable arrays downwards ------------------------------- // If the type was either table or foreign key then a new chado variable was // generated this variable will have it's own expandable array's which need to // be moved down and merged with the base objects expandable arrays. // Thus, check all nested objects for expandable arrays // and if they have them, move them downwards. foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { // The current nested object has expandable arrays. if (isset($field_value->expandable_fields)) { // Move expandable fields downwards. if (isset($field_value->expandable_fields) and is_array($field_value->expandable_fields)) { // If the current object has it's own expandable fields then merge them. if (isset($object->expandable_fields)) { $object->expandable_fields = array_merge( $object->expandable_fields, $object->{$field_name}->expandable_fields ); unset($object->{$field_name}->expandable_fields); } // Otherwise, just move the expandable fields downwards. else { $object->expandable_fields = $object->{$field_name}->expandable_fields; unset($object->{$field_name}->expandable_fields); } } // Move expandable foreign keys downwards. if (isset($field_value->expandable_foreign_keys) and is_array($field_value->expandable_foreign_keys)) { // If the current object has it's own expandable foreign keys then // merge them. if (isset($object->expandable_foreign_keys)) { $object->expandable_foreign_keys = array_merge( $object->expandable_foreign_keys, $object->{$field_name}->expandable_foreign_keys ); unset($object->{$field_name}->expandable_foreign_keys); } // Otherwise, just move the expandable foreign keys downwards. else { $object->expandable_foreign_keys = $object->{$field_name}->expandable_foreign_keys; unset($object->{$field_name}->expandable_foreign_keys); } } // Move expandable tables downwards. if (isset($field_value->expandable_tables) and is_array($field_value->expandable_tables)) { // If the current object has it's own expandable tables then merge them. if (isset($object->expandable_tables)) { $object->expandable_tables = array_merge( $object->expandable_tables, $object->{$field_name}->expandable_tables ); unset($object->{$field_name}->expandable_tables); } // Otherwise, just move the expandable tables downwards. else { $object->expandable_tables = $object->{$field_name}->expandable_tables; unset($object->{$field_name}->expandable_tables); } } // Move expandable nodes downwards. if (isset($field_value->expandable_nodes) and is_array($field_value->expandable_nodes)) { // If the current object has it's own expandable tables then merge them. if (isset($object->expandable_nodes)) { $object->expandable_nodes = array_merge( $object->expandable_nodes, $object->{$field_name}->expandable_nodes ); unset($object->{$field_name}->expandable_nodes); } // Otherwise, just move the expandable tables downwards. else { $object->expandable_nodes = $object->{$field_name}->expandable_nodes; unset($object->{$field_name}->expandable_nodes); } } } } } // Move extended array downwards ---------------------------------- // This tells us what we have expanded (ie: that we succeeded) // and is needed to remove the entry from the expandable array. // If there is no expanded field in the current object then check any of the // nested objects and move it down. if (!property_exists($object, 'expanded')) { // It's a nested object if the value is an object. foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { // Check if the current nested object has an expanded array. if (isset($field_value->expanded)) { // If so, then move it downwards. $object->expanded = $field_value->expanded; unset($field_value->expanded); } } } } // Check again if there is an expanded field in the current object // We check again because it might have been moved downwards above. if (property_exists($object, 'expanded')) { // If so, then remove the expanded identifier from the correct expandable // array.. $expandable_name = 'expandable_' . $type . 's'; if (property_exists($object, $expandable_name) and $object->{$expandable_name}) { $key_to_remove = array_search($object->expanded, $object->{$expandable_name}); unset($object->{$expandable_name}[$key_to_remove]); unset($object->expanded); } } // Finally, Return the object! return $object; }