From ad6c5ae443e139a2fddaef5c5e69444575b0050b Mon Sep 17 00:00:00 2001 From: Aaron Bauman <aaron@messageagency.com> Date: Thu, 9 Aug 2018 17:37:05 -0400 Subject: [PATCH] Issue #2975835: Compatibility with drush 9 Initial pass at updating to drush 9 compatibility updates salesforce.drush functions only. Still todo: salesforce_pull, salesforce_push, salesforce_mapping. These should be faster now that the framework is in place. --- composer.json | 45 +- drush.services.yml | 6 + salesforce.drush.inc | 36 ++ src/Commands/SalesforceCommands.php | 613 ++++++++++++++++++++++++++++ 4 files changed, 681 insertions(+), 19 deletions(-) create mode 100644 drush.services.yml create mode 100644 src/Commands/SalesforceCommands.php diff --git a/composer.json b/composer.json index 3ad8d42a..e01e804d 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,29 @@ { - "name": "drupal/salesforce", - "description": "Provides Drupal modules to integrate with Salesforce.", - "type": "drupal-module", - "homepage": "https://drupal.org/project/salesforce", - "authors": [ - { - "name": "Aaron Bauman (aaronbauman)", - "homepage": "https://www.drupal.org/u/aaronbauman", - "role": "Maintainer" + "name": "drupal/salesforce", + "description": "Provides Drupal modules to integrate with Salesforce.", + "type": "drupal-module", + "homepage": "https://drupal.org/project/salesforce", + "authors": [ + { + "name": "Aaron Bauman (aaronbauman)", + "homepage": "https://www.drupal.org/u/aaronbauman", + "role": "Maintainer" + }, + { + "name": "Alexander Rhodes (ironsizide)", + "homepage": "https://www.drupal.org/u/ironsizide", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://drupal.org/project/issues/salesforce", + "source": "http://cgit.drupalcode.org/salesforce" }, - { - "name": "Alexander Rhodes (ironsizide)", - "homepage": "https://www.drupal.org/u/ironsizide", - "role": "Maintainer" + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9" + } + } } - ], - "support": { - "issues": "https://drupal.org/project/issues/salesforce", - "source": "http://cgit.drupalcode.org/salesforce" - } -} +} \ No newline at end of file diff --git a/drush.services.yml b/drush.services.yml new file mode 100644 index 00000000..c671c4c5 --- /dev/null +++ b/drush.services.yml @@ -0,0 +1,6 @@ +services: + salesforce.commands: + class: \Drupal\salesforce\Commands\SalesforceCommands + arguments: ['@salesforce.client'] + tags: + - { name: drush.command } diff --git a/salesforce.drush.inc b/salesforce.drush.inc index 03e569c8..fe763969 100644 --- a/salesforce.drush.inc +++ b/salesforce.drush.inc @@ -10,6 +10,8 @@ use Drupal\salesforce\SelectQuery; /** * Implements hook_drush_command(). + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function salesforce_drush_command() { $items['sf-rest-version'] = [ @@ -146,8 +148,11 @@ raw: Display the complete, raw describe response.", * List the resources available for the specified API version. * * This command provides the name and URI of each resource. + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_list_resources() { + _drush_salesforce_deprecated(); $salesforce = \Drupal::service('salesforce.client'); $resources = $salesforce->listResources(); if ($resources) { @@ -172,8 +177,12 @@ function drush_salesforce_sf_list_resources() { * * @param string $object_name * The name of a Salesforce object to query. + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_describe_object($object_name = NULL) { + _drush_salesforce_deprecated(); + if (!$object_name) { return drush_log('Please specify an object as an argument.', 'error'); } @@ -249,8 +258,12 @@ function drush_salesforce_sf_describe_object($object_name = NULL) { /** * Displays information about the REST API version the site is using. + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_rest_version() { + _drush_salesforce_deprecated(); + $salesforce = \Drupal::service('salesforce.client'); $version_id = $salesforce->getApiVersion(); $versions = $salesforce->getVersions(); @@ -270,10 +283,14 @@ function drush_salesforce_sf_rest_version() { * * This command lists Salesforce objects that are available in your organization * and available to the logged-in user. + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_list_objects() { + _drush_salesforce_deprecated(); $salesforce = \Drupal::service('salesforce.client'); if ($objects = $salesforce->objects()) { + print_r($objects); drush_print('The following objects are available in your organization and available to the logged-in user.'); $rows[] = ['Name', 'Label', 'Label Plural']; foreach ($objects as $object) { @@ -298,8 +315,11 @@ function drush_salesforce_sf_list_objects() { * The object type name, e.g. Account * @param $id * The Salesforce ID + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_read_object($id) { + _drush_salesforce_deprecated(); $salesforce = \Drupal::service('salesforce.client'); try { $name = $salesforce->getObjectTypeName(new SFID($id)); @@ -323,8 +343,11 @@ function drush_salesforce_sf_read_object($id) { * The object type name, e.g. Account * @param $data * The object data, or '-' to read from stdin + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_create_object($name, $data) { + _drush_salesforce_deprecated(); if ($data == '-') { $data = stream_get_contents(STDIN); @@ -360,8 +383,11 @@ function drush_salesforce_sf_create_object($name, $data) { * * @param $name * The object type name, e.g. Account + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_query_object($name) { + _drush_salesforce_deprecated(); $salesforce = \Drupal::service('salesforce.client'); $query = new SelectQuery($name); @@ -419,8 +445,11 @@ function drush_salesforce_sf_query_object($name) { * * @param $query * The query to execute + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function drush_salesforce_sf_execute_query($query = NULL) { + _drush_salesforce_deprecated(); if (!$query) { return drush_log('Please specify a query as an argument.', 'error'); } @@ -441,8 +470,11 @@ function drush_salesforce_sf_execute_query($query = NULL) { * @param string $name * * @return SalesforceMappingInterface + * + * @deprecated Support for drush 8 is deprecated and will be removed in a future release. */ function _salesforce_drush_get_mapping($name = NULL) { + _drush_salesforce_deprecated(); $mapping_storage = \Drupal::service('entity_type.manager') ->getStorage('salesforce_mapping'); @@ -465,3 +497,7 @@ function _salesforce_drush_get_mapping($name = NULL) { } return $mapping; } + +function _drush_salesforce_deprecated() { + trigger_error('Salesforce module support for Drush 8 is deprecated and will be removed in a future release', E_DEPRECATED); +} diff --git a/src/Commands/SalesforceCommands.php b/src/Commands/SalesforceCommands.php new file mode 100644 index 00000000..7e9f43be --- /dev/null +++ b/src/Commands/SalesforceCommands.php @@ -0,0 +1,613 @@ +<?php + +namespace Drupal\salesforce\Commands; + +use Consolidation\OutputFormatters\Formatters\TableFormatter; +use Consolidation\OutputFormatters\Formatters\VarDumpFormatter; +use Consolidation\OutputFormatters\Options\FormatterOptions; +use Consolidation\OutputFormatters\StructuredData\PropertyList; +use Consolidation\OutputFormatters\StructuredData\RowsOfFields; +use Consolidation\OutputFormatters\StructuredData\RowsOfFieldsWithMetadata; +use Drupal\salesforce\Rest\RestClient; +use Drupal\salesforce\SelectQuery; +use Drupal\salesforce\SFID; +use Drush\Commands\DrushCommands; +use Drush\Exceptions\UserAbortException; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Input\Input; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Translation\Util\ArrayConverter; + +/** + * A Drush commandfile. + * + * In addition to this file, you need a drush.services.yml + * in root of your module, and a composer.json file that provides the name + * of the services file to use. + * + * See these files for an example of injecting Drupal services: + * - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php + * - http://cgit.drupalcode.org/devel/tree/drush.services.yml + */ +class SalesforceCommands extends DrushCommands { + + /** @var \Drupal\salesforce\Rest\RestClient */ + protected $client; + + public function __construct(RestClient $client) { + $this->client = $client; + } + + /** + * Display information about the current REST API version. + * + * @command salesforce:rest-version + * @aliases sfrv,sf-rest-version + * @field-labels + * label: Label + * url: Path + * version: Version + * login_url: Login URL + * latest: Latest Version? + * @default-fields label,url,version,login_url,latest + * + * @return \Consolidation\OutputFormatters\StructuredData\PropertyList + */ + public function restVersion() { + $version_id = $this->client->getApiVersion(); + $versions = $this->client->getVersions(); + $version = $versions[$version_id]; + $latest = array_pop($versions); + foreach ($version as $key => $value) { + $rows[$key] = $value; + } + $rows['login_url'] = $this->client->getLoginUrl(); + $rows['latest'] = strcmp($version_id, $latest['version']) ? $latest['version'] : 'Yes'; + return new PropertyList($rows); + } + + /** + * List the objects that are available in your organization and available to the logged-in user. + * + * @command salesforce:list-objects + * @aliases sflo,sf-list-objects + * @field-labels + * activateable: Activateable + * createable: Createable + * custom: Custom + * customSetting: CustomSetting + * deletable: Deletable + * deprecatedAndHidden: DeprecatedAndHidden + * feedEnabled: FeedEnabled + * hasSubtypes: HasSubtypes + * isSubtype: IsSubtype + * keyPrefix: KeyPrefix + * label: Label + * labelPlural: LabelPlural + * layoutable: Layoutable + * mergeable: Mergeable + * mruEnabled: MruEnabled + * name: Name + * queryable: Queryable + * replicateable: Replicateable + * retrieveable: Retrieveable + * searchable: Searchable + * triggerable: Triggerable + * undeletable: Undeletable + * updateable: Updateable + * urls: URLs + * @default-fields name,label,labelPlural + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function listObjects() { + if ($objects = $this->client->objects()) { + foreach ($objects as $name => $object) { + $rows[$name] = $object; + $rows[$name]['urls'] = new TableCell(implode("\n", $rows[$name]['urls']) . "\n"); + } + return new RowsOfFields($rows); + } + throw new \Exception('Could not load any information about available objects.'); + } + + /** + * @hook interact salesforce:describe-object + */ + public function interactDescribeObject(Input $input, Output $output) { + return $this->interactObject($input, $output); + } + + /** + * @hook interact salesforce:describe-fields + */ + public function interactDescribeFields(Input $input, Output $output) { + return $this->interactObject($input, $output); + } + + /** + * @hook interact salesforce:describe-metadata + */ + public function interactDescribeMetadata(Input $input, Output $output) { + return $this->interactObject($input, $output); + } + + /** + * @hook interact salesforce:describe-record-types + */ + public function interactDescribeRecordTypes(Input $input, Output $output) { + return $this->interactObject($input, $output); + } + + /** + * @hook interact salesforce:dump-object + */ + public function interactDumpObject(Input $input, Output $output) { + return $this->interactObject($input, $output); + } + + /** + * If there's a way to attach multiple hooks to one method, please do it here! + */ + protected function interactObject(Input $input, Output $output, $message = 'Enter a Salesforce object to describe') { + if (!$input->getArgument('object')) { + if (!$answer = $this->io()->ask($message)) { + throw new UserAbortException(); + } + $input->setArgument('object', $answer); + } + } + + /** + * Retrieve all the metadata for an object, including information about each field, URLs, and child relationships. + * + * @param $object + * The object name in Salesforce. + * @param array $options An associative array of options whose values come from cli, aliases, config, etc. + * @option output + * Specify an output type. + * Options are: + * info: (default) Display metadata about an object + * fields: Display information about fields that are part of the object + * field: Display information about a specific field that is part of an object + * raw: Display the complete, raw describe response. + * @option field + * For "field" output type, specify a fieldname. + * @usage drush sfdo Contact + * Show metadata about Contact SObject type. + * @usage drush sfdo Contact --output=fields + * Show addtional metadata about Contact fields. + * @usage drush sfdo Contact --output=field --field=Email + * Show full metadata about Contact.Email field. + * @usage drush sfdo Contact --output=raw + * Display the full metadata for Contact SObject type. + * + * @command salesforce:describe-object-deprecated + * @deprecated Use describeFields, describeMetadata, describeRecordTypes, dumpObject + */ + public function describeObject($object, array $options = ['output' => null, 'field' => null]) { + return $this->describeFields($object); + } + + /** + * Dump the raw describe response for given object. + * + * @command salesforce:dump-object + * @aliases sf-dump-object + */ + public function dumpObject($object) { + $objectDescription = $this->client->objectDescribe($object); + if (!is_object($objectDescription)) { + throw new \Exception(dt('Could not load data for object !object', ['!object' => $object])); + } + $this->output()->writeln(print_r($objectDescription->data, 1)); + } + + /** + * Retrieve object record types. + * + * @param $object + * The object name in Salesforce. + * + * @command salesforce:describe-record-types + * @aliases sfdrt,sf-describe-record-types + * + * @field-labels + * active: Active + * available: Available + * defaultRecordTypeMapping: Default + * developerName: Developer Name + * master: Master + * name: Name + * recordTypeId: Id + * urls: URLs + * + * @default-fields name,recordTypeId,developerName,active,available,defaultRecordTypeMapping,master + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function describeRecordTypes($object) { + $objectDescription = $this->client->objectDescribe($object); + if (!is_object($objectDescription)) { + throw new \Exception(dt('Could not load data for object !object', ['!object' => $object])); + } + $data = $objectDescription->data['recordTypeInfos']; + // Return if we cannot load any data. + $rows = []; + foreach ($data as $rt) { + $rt['urls'] = implode("\n", $rt['urls']); + $rows[$rt['developerName']] = $rt; + } + return new RowsOfFields($rows); + } + + /** + * Retrieve object metadata. + * + * @param $object + * The object name in Salesforce. + * + * @command salesforce:describe-metadata + * @aliases sfdom,sf-describe-metadata + * + * @field-labels + * actionOverrides: ActionOverrides + * activateable: Activateable + * compactLayoutable: CompactLayoutable + * createable: Createable + * custom: Custom + * customSetting: CustomSetting + * deletable: Deletable + * deprecatedAndHidden: DeprecatedAndHidden + * feedEnabled: FeedEnabled + * hasSubtypes: HasSubtypes + * isSubtype: IsSubtype + * keyPrefix: KeyPrefix + * label: Label + * labelPlural: LabelPlural + * layoutable: Layoutable + * listviewable: Listviewable + * lookupLayoutable: LookupLayoutable + * mergeable: Mergeable + * mruEnabled: MruEnabled + * name: Name + * namedLayoutInfos: NamedLayoutInfos + * networkScopeFieldName: NetworkScopeFieldName + * queryable: Queryable + * replicateable: Replicateable + * retrieveable: Retrieveable + * searchLayoutable: SearchLayoutable + * searchable: Searchable + * supportedScopes: SupportedScopes + * triggerable: Triggerable + * undeletable: Undeletable + * updateable: Updateable + * urls: Urls + * + * @return \Consolidation\OutputFormatters\StructuredData\PropertyList + */ + public function describeMetadata($object) { + $objectDescription = $this->client->objectDescribe($object); + if (!is_object($objectDescription)) { + throw new \Exception(dt('Could not load data for object !object', ['!object' => $object])); + } + $data = $objectDescription->data; + // Return if we cannot load any data. + unset($data['fields'], $data['childRelationships'], $data['recordTypeInfos']); + foreach ($data as $k => &$v) { + if ($k == 'supportedScopes') { + array_walk($v, function(&$value, $key) { + $value = $value['name'] . ' (' . $value['label'] . ')'; + }); + } + if (is_array($v)) { + if (empty($v)) { + $v = ''; + } + else { + $v = implode("\n", $v) . "\n"; + } + } + } + return new PropertyList($data); + } + + /** + * Retrieve all the metadata for an object, including information about each field, URLs, and child relationships. + * + * @param $object + * The object name in Salesforce. + * + * @command salesforce:describe-fields + * @aliases salesforce:describe-object,sfdo,sfdf,sf-describe-fields + * @usage drush sfdo Contact + * Show metadata about Contact SObject type. + * + * @field-labels + * aggregatable: Aggregatable + * aiPredictionField: AiPredictionField + * autoNumber: AutoNumber + * byteLength: ByteLength + * calculated: Calculated + * calculatedFormula: CalculatedFormula + * cascadeDelete: CascadeDelete + * caseSensitive: CaseSensitive + * compoundFieldName: CompoundFieldName + * controllerName: ControllerName + * createable: Createable + * custom: Custom + * defaultValue: DefaultValue + * defaultValueFormula: DefaultValueFormula + * defaultedOnCreate: DefaultedOnCreate + * dependentPicklist: DependentPicklist + * deprecatedAndHidden: DeprecatedAndHidden + * digits: Digits + * displayLocationInDecimal: DisplayLocationInDecimal + * encrypted: Encrypted + * externalId: ExternalId + * extraTypeInfo: ExtraTypeInfo + * filterable: Filterable + * filteredLookupInfo: FilteredLookupInfo + * formulaTreatNullNumberAsZero: FormulaTreatNullNumberAsZero + * groupable: Groupable + * highScaleNumber: HighScaleNumber + * htmlFormatted: HtmlFormatted + * idLookup: IdLookup + * inlineHelpText: InlineHelpText + * label: Label + * length: Length + * mask: Mask + * maskType: MaskType + * name: Name + * nameField: NameField + * namePointing: NamePointing + * nillable: Nillable + * permissionable: Permissionable + * picklistValues: PicklistValues + * polymorphicForeignKey: PolymorphicForeignKey + * precision: Precision + * queryByDistance: QueryByDistance + * referenceTargetField: ReferenceTargetField + * referenceTo: ReferenceTo + * relationshipName: RelationshipName + * relationshipOrder: RelationshipOrder + * restrictedDelete: RestrictedDelete + * restrictedPicklist: RestrictedPicklist + * scale: Scale + * searchPrefilterable: SearchPrefilterable + * soapType: SoapType + * sortable: Sortable + * type: Type + * unique: Unique + * updateable: Updateable + * writeRequiresMasterRead: WriteRequiresMasterRead + * + * @default-fields label,name,type + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function describeFields($object) { + $objectDescription = $this->client->objectDescribe($object); + // Return if we cannot load any data. + if (!is_object($objectDescription)) { + throw new \Exception(dt('Could not load data for object !object', ['!object' => $object])); + } + + foreach ($objectDescription->getFields() as $field => $data) { + if (!empty($data['picklistValues'])) { + $fix_data = []; + foreach ($data['picklistValues'] as $value) { + $fix_data[] = $value['value'] . ' (' . $value['label'] . ')'; + } + $data['picklistValues'] = $fix_data; + } + foreach ($data as $k => &$v) { + if (is_array($v)) { + $v = implode("\n", $v); + } + } + $rows[$field] = $data; + } + return new RowsOfFields($rows); + } + + /** + * Lists the resources available for the current API version. + * + * @command salesforce:list-resources + * @aliases sflr,sf-list-resources + * @field-labels + * resource: Resource + * url: URL + * @default-fields resource,url + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function listResources() { + $resources = $this->client->listResources(); + if ($resources) { + foreach ($resources->resources as $resource => $url) { + $rows[$url] = ['resource' => $resource, 'url' => $url]; + } + $this->output()->writeln("The following resources are available:"); + return new RowsOfFields($rows); + } + throw new \Exception('Could not obtain a list of resources!'); + } + + /** + * @hook interact salesforce:read-object + */ + public function interactReadObject(Input $input, Output $output) { + if (!$input->getArgument('id')) { + if (!$answer = $this->io()->ask('Enter the Salesforce id to fetch')) { + throw new UserAbortException(); + } + $input->setArgument('id', $answer); + } + } + + /** + * Retrieve all the data for an object with a specific ID. + * + * @command salesforce:read-object + * @aliases sfro,sf-read-object + */ + public function readObject($id) { + $name = $this->client->getObjectTypeName(new SFID($id)); + if ($object = $this->client->objectRead($name, $id)) { + $this->output()->writeln(dt("!type with id !id", [ + '!type' => $object->type(), + '!id' => $object->id(), + ])); + $this->output()->writeln(print_r($object->fields(), 1)); + } + return; + } + + /** + * @hook interact salesforce:create-object + */ + public function interactCreateObject(Input $input, Output $output) { + $format = $input->getOption('format'); + if (empty($format)) { + $input->setOption('format', 'query'); + $format = 'query'; + } + elseif (!in_array($input->getOption('format'), ['query', 'json'])) { + throw new \Exception('Invalid format'); + } + + $this->interactObject($input, $output, 'Enter the object type to be created'); + + if (!$data = $this->io()->ask('Enter the object data to be created')) { + throw new UserAbortException(); + } + $params = []; + switch ($format) { + case 'query': + parse_str($data, $params); + if (empty($params)) { + throw new \Exception(dt('Error when decoding data')); + } + break; + + case 'json': + $params = json_decode($data, TRUE); + if (json_last_error()) { + throw new \Exception(dt('Error when decoding data: !error', ['!error' => json_last_error_msg()])); + } + break; + + } + $this->input()->setArgument('data', $params); + } + + /** + * Create an object with specified data. + * + * @param string $object + * The object type name in Salesforce (e.g. Account). + * @param array $data + * The data to use when creating the object (default is JSON format). Use '-' to read the data from STDIN. + * @param array $options An associative array of options whose values come from cli, aliases, config, etc. + * @option format + * Format to parse the object. Use "json" for JSON (default) or "query" for data formatted like a query string, e.g. 'Company=Foo&LastName=Bar'. + * Defaults to query. + * + * @command salesforce:create-object + * @aliases sfco,sf-create-object + */ + public function createObject($object, $data, array $options = ['format' => 'query']) { + if ($result = $this->client->objectCreate($object, $data)) { + $this->output->writeln(dt('Successfully created !object with id !id', ['!object' => $object, '!id' => (string)$result])); + } + } + + /** + * @hook interact salesforce:query-object + */ + public function interactQueryObject(Input $input, Output $output) { + return $this->interactObject($input, $output, 'Enter the object to be queried'); + } + /** + * Query an object using SOQL with specified conditions. + * + * @param $object + * The object type name in Salesforce (e.g. Account). + * @param array $options An associative array of options whose values come from cli, aliases, config, etc. + * @option format + * Format to output the objects. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON. + * @option where + * A WHERE clause to add to the SOQL query + * @option fields + * A comma-separated list fields to select in the SOQL query. If absent, an API call is used to find all fields + * @option limit + * Integer limit on the number of results to return for the query. + * @option order + * Comma-separated fields by which to sort results. Make sure to enclose in quotes for any whitespace. + * + * @command salesforcef:query-object + * @aliases sfqo,sf-query-object + */ + public function queryObject($object, array $options = ['format' => null, 'where' => null, 'fields' => null, 'limit' => null, 'order' => null]) { + $query = new SelectQuery($object); + + if (!$options['fields']) { + $object = $this->client->objectDescribe($object); + $query->fields = array_keys($object->getFields()); + } + else { + $query->fields = explode(',', $options['fields']); + // Query must include Id. + if (!in_array('Id', $query->fields)) { + $query->fields[] = 'Id'; + } + } + + $query->limit = $options['limit']; + + if ($options['where']) { + $query->conditions = [[$options['where']]]; + } + + if ($options['order']) { + $query->order = []; + $orders = explode(',', $options['order']); + foreach ($orders as $order) { + list($field, $dir) = preg_split('/\s+/', $order, 2); + $query->order[$field] = $dir; + } + } + + $result = $this->client->query($query); + foreach ($result->records() as $sfid => $record) { + $this->output()->writeln(print_r($record->fields(), 1)); + } + $pretty_query = str_replace('+', ' ', (string) $query); + if (!$options['fields']) { + $fields = implode(',', $query->fields); + $pretty_query = str_replace($fields, ' * ', $pretty_query); + } + $this->output()->writeln(dt("Showing !size of !total records for query:\n!query", [ + '!size' => count($result->records()), + '!total' => $result->size(), + '!query' => $pretty_query, + ])); + } + + /** + * Execute a SOQL query. + * + * @param string $query + * The query to execute. + * + * @command salesforce:execute-query + * @aliases sfeq,soql,sf-execute-query + */ + public function executeQuery($query) { + $this->output()->writeln(print_r($this->client->apiCall('query?q=' . urlencode($query)), 1)); + } + +} -- GitLab